Implement Tournament Manager
[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
236 #ifdef WIN32
237        extern void ConsoleCreate();
238 #endif
239
240 ChessProgramState *WhitePlayer();
241 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
242 int VerifyDisplayMode P(());
243
244 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
245 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
246 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
247 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
248 void ics_update_width P((int new_width));
249 extern char installDir[MSG_SIZ];
250 VariantClass startVariant; /* [HGM] nicks: initial variant */
251
252 extern int tinyLayout, smallLayout;
253 ChessProgramStats programStats;
254 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
255 int endPV = -1;
256 static int exiting = 0; /* [HGM] moved to top */
257 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
258 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
259 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
260 int partnerHighlight[2];
261 Boolean partnerBoardValid = 0;
262 char partnerStatus[MSG_SIZ];
263 Boolean partnerUp;
264 Boolean originalFlip;
265 Boolean twoBoards = 0;
266 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
267 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
268 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
269 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
270 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
271 int opponentKibitzes;
272 int lastSavedGame; /* [HGM] save: ID of game */
273 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
274 extern int chatCount;
275 int chattingPartner;
276 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
277 char lastMsg[MSG_SIZ];
278 ChessSquare pieceSweep = EmptySquare;
279 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
280 int promoDefaultAltered;
281
282 /* States for ics_getting_history */
283 #define H_FALSE 0
284 #define H_REQUESTED 1
285 #define H_GOT_REQ_HEADER 2
286 #define H_GOT_UNREQ_HEADER 3
287 #define H_GETTING_MOVES 4
288 #define H_GOT_UNWANTED_HEADER 5
289
290 /* whosays values for GameEnds */
291 #define GE_ICS 0
292 #define GE_ENGINE 1
293 #define GE_PLAYER 2
294 #define GE_FILE 3
295 #define GE_XBOARD 4
296 #define GE_ENGINE1 5
297 #define GE_ENGINE2 6
298
299 /* Maximum number of games in a cmail message */
300 #define CMAIL_MAX_GAMES 20
301
302 /* Different types of move when calling RegisterMove */
303 #define CMAIL_MOVE   0
304 #define CMAIL_RESIGN 1
305 #define CMAIL_DRAW   2
306 #define CMAIL_ACCEPT 3
307
308 /* Different types of result to remember for each game */
309 #define CMAIL_NOT_RESULT 0
310 #define CMAIL_OLD_RESULT 1
311 #define CMAIL_NEW_RESULT 2
312
313 /* Telnet protocol constants */
314 #define TN_WILL 0373
315 #define TN_WONT 0374
316 #define TN_DO   0375
317 #define TN_DONT 0376
318 #define TN_IAC  0377
319 #define TN_ECHO 0001
320 #define TN_SGA  0003
321 #define TN_PORT 23
322
323 char*
324 safeStrCpy( char *dst, const char *src, size_t count )
325 { // [HGM] made safe
326   int i;
327   assert( dst != NULL );
328   assert( src != NULL );
329   assert( count > 0 );
330
331   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
332   if(  i == count && dst[count-1] != NULLCHAR)
333     {
334       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
335       if(appData.debugMode)
336       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
337     }
338
339   return dst;
340 }
341
342 /* Some compiler can't cast u64 to double
343  * This function do the job for us:
344
345  * We use the highest bit for cast, this only
346  * works if the highest bit is not
347  * in use (This should not happen)
348  *
349  * We used this for all compiler
350  */
351 double
352 u64ToDouble(u64 value)
353 {
354   double r;
355   u64 tmp = value & u64Const(0x7fffffffffffffff);
356   r = (double)(s64)tmp;
357   if (value & u64Const(0x8000000000000000))
358        r +=  9.2233720368547758080e18; /* 2^63 */
359  return r;
360 }
361
362 /* Fake up flags for now, as we aren't keeping track of castling
363    availability yet. [HGM] Change of logic: the flag now only
364    indicates the type of castlings allowed by the rule of the game.
365    The actual rights themselves are maintained in the array
366    castlingRights, as part of the game history, and are not probed
367    by this function.
368  */
369 int
370 PosFlags(index)
371 {
372   int flags = F_ALL_CASTLE_OK;
373   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
374   switch (gameInfo.variant) {
375   case VariantSuicide:
376     flags &= ~F_ALL_CASTLE_OK;
377   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
378     flags |= F_IGNORE_CHECK;
379   case VariantLosers:
380     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
381     break;
382   case VariantAtomic:
383     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
384     break;
385   case VariantKriegspiel:
386     flags |= F_KRIEGSPIEL_CAPTURE;
387     break;
388   case VariantCapaRandom:
389   case VariantFischeRandom:
390     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
391   case VariantNoCastle:
392   case VariantShatranj:
393   case VariantCourier:
394   case VariantMakruk:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP;
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 int ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
465 long timeControl_2; /* [AS] Allow separate time controls */
466 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
467 long timeRemaining[2][MAX_MOVES];
468 int matchGame = 0, nextGame = 0, roundNr = 0;
469 Boolean waitingForGame = FALSE;
470 TimeMark programStartTime, pauseStart;
471 char ics_handle[MSG_SIZ];
472 int have_set_title = 0;
473
474 /* animateTraining preserves the state of appData.animate
475  * when Training mode is activated. This allows the
476  * response to be animated when appData.animate == TRUE and
477  * appData.animateDragging == TRUE.
478  */
479 Boolean animateTraining;
480
481 GameInfo gameInfo;
482
483 AppData appData;
484
485 Board boards[MAX_MOVES];
486 /* [HGM] Following 7 needed for accurate legality tests: */
487 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
488 signed char  initialRights[BOARD_FILES];
489 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
490 int   initialRulePlies, FENrulePlies;
491 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
492 int loadFlag = 0;
493 int shuffleOpenings;
494 int mute; // mute all sounds
495
496 // [HGM] vari: next 12 to save and restore variations
497 #define MAX_VARIATIONS 10
498 int framePtr = MAX_MOVES-1; // points to free stack entry
499 int storedGames = 0;
500 int savedFirst[MAX_VARIATIONS];
501 int savedLast[MAX_VARIATIONS];
502 int savedFramePtr[MAX_VARIATIONS];
503 char *savedDetails[MAX_VARIATIONS];
504 ChessMove savedResult[MAX_VARIATIONS];
505
506 void PushTail P((int firstMove, int lastMove));
507 Boolean PopTail P((Boolean annotate));
508 void CleanupTail P((void));
509
510 ChessSquare  FIDEArray[2][BOARD_FILES] = {
511     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
512         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
513     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
514         BlackKing, BlackBishop, BlackKnight, BlackRook }
515 };
516
517 ChessSquare twoKingsArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackKing, BlackKnight, BlackRook }
522 };
523
524 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
526         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
527     { BlackRook, BlackMan, BlackBishop, BlackQueen,
528         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 };
530
531 ChessSquare SpartanArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
533         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
534     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
535         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 };
537
538 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
542         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 };
544
545 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
546     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
547         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
548     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
549         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 };
551
552 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
554         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackMan, BlackFerz,
556         BlackKing, BlackMan, BlackKnight, BlackRook }
557 };
558
559
560 #if (BOARD_FILES>=10)
561 ChessSquare ShogiArray[2][BOARD_FILES] = {
562     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
563         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
564     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
565         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 };
567
568 ChessSquare XiangqiArray[2][BOARD_FILES] = {
569     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
570         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
571     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
572         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 };
574
575 ChessSquare CapablancaArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
577         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
579         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 };
581
582 ChessSquare GreatArray[2][BOARD_FILES] = {
583     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
584         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
585     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
586         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 };
588
589 ChessSquare JanusArray[2][BOARD_FILES] = {
590     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
591         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
592     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
593         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 };
595
596 #ifdef GOTHIC
597 ChessSquare GothicArray[2][BOARD_FILES] = {
598     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
599         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
600     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
601         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
602 };
603 #else // !GOTHIC
604 #define GothicArray CapablancaArray
605 #endif // !GOTHIC
606
607 #ifdef FALCON
608 ChessSquare FalconArray[2][BOARD_FILES] = {
609     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
610         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
611     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
612         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
613 };
614 #else // !FALCON
615 #define FalconArray CapablancaArray
616 #endif // !FALCON
617
618 #else // !(BOARD_FILES>=10)
619 #define XiangqiPosition FIDEArray
620 #define CapablancaArray FIDEArray
621 #define GothicArray FIDEArray
622 #define GreatArray FIDEArray
623 #endif // !(BOARD_FILES>=10)
624
625 #if (BOARD_FILES>=12)
626 ChessSquare CourierArray[2][BOARD_FILES] = {
627     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
628         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
629     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
630         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
631 };
632 #else // !(BOARD_FILES>=12)
633 #define CourierArray CapablancaArray
634 #endif // !(BOARD_FILES>=12)
635
636
637 Board initialPosition;
638
639
640 /* Convert str to a rating. Checks for special cases of "----",
641
642    "++++", etc. Also strips ()'s */
643 int
644 string_to_rating(str)
645   char *str;
646 {
647   while(*str && !isdigit(*str)) ++str;
648   if (!*str)
649     return 0;   /* One of the special "no rating" cases */
650   else
651     return atoi(str);
652 }
653
654 void
655 ClearProgramStats()
656 {
657     /* Init programStats */
658     programStats.movelist[0] = 0;
659     programStats.depth = 0;
660     programStats.nr_moves = 0;
661     programStats.moves_left = 0;
662     programStats.nodes = 0;
663     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
664     programStats.score = 0;
665     programStats.got_only_move = 0;
666     programStats.got_fail = 0;
667     programStats.line_is_book = 0;
668 }
669
670 void
671 CommonEngineInit()
672 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
673     if (appData.firstPlaysBlack) {
674         first.twoMachinesColor = "black\n";
675         second.twoMachinesColor = "white\n";
676     } else {
677         first.twoMachinesColor = "white\n";
678         second.twoMachinesColor = "black\n";
679     }
680
681     first.other = &second;
682     second.other = &first;
683
684     { float norm = 1;
685         if(appData.timeOddsMode) {
686             norm = appData.timeOdds[0];
687             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
688         }
689         first.timeOdds  = appData.timeOdds[0]/norm;
690         second.timeOdds = appData.timeOdds[1]/norm;
691     }
692
693     if(programVersion) free(programVersion);
694     if (appData.noChessProgram) {
695         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
696         sprintf(programVersion, "%s", PACKAGE_STRING);
697     } else {
698       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
699       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
700       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
701     }
702 }
703
704 void
705 UnloadEngine(ChessProgramState *cps)
706 {
707         /* Kill off first chess program */
708         if (cps->isr != NULL)
709           RemoveInputSource(cps->isr);
710         cps->isr = NULL;
711
712         if (cps->pr != NoProc) {
713             ExitAnalyzeMode();
714             DoSleep( appData.delayBeforeQuit );
715             SendToProgram("quit\n", cps);
716             DoSleep( appData.delayAfterQuit );
717             DestroyChildProcess(cps->pr, cps->useSigterm);
718         }
719         cps->pr = NoProc;
720         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
721 }
722
723 void
724 ClearOptions(ChessProgramState *cps)
725 {
726     int i;
727     cps->nrOptions = cps->comboCnt = 0;
728     for(i=0; i<MAX_OPTIONS; i++) {
729         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
730         cps->option[i].textValue = 0;
731     }
732 }
733
734 char *engineNames[] = {
735 "first",
736 "second"
737 };
738
739 void
740 InitEngine(ChessProgramState *cps, int n)
741 {   // [HGM] all engine initialiation put in a function that does one engine
742
743     ClearOptions(cps);
744
745     cps->which = engineNames[n];
746     cps->maybeThinking = FALSE;
747     cps->pr = NoProc;
748     cps->isr = NULL;
749     cps->sendTime = 2;
750     cps->sendDrawOffers = 1;
751
752     cps->program = appData.chessProgram[n];
753     cps->host = appData.host[n];
754     cps->dir = appData.directory[n];
755     cps->initString = appData.engInitString[n];
756     cps->computerString = appData.computerString[n];
757     cps->useSigint  = TRUE;
758     cps->useSigterm = TRUE;
759     cps->reuse = appData.reuse[n];
760     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
761     cps->useSetboard = FALSE;
762     cps->useSAN = FALSE;
763     cps->usePing = FALSE;
764     cps->lastPing = 0;
765     cps->lastPong = 0;
766     cps->usePlayother = FALSE;
767     cps->useColors = TRUE;
768     cps->useUsermove = FALSE;
769     cps->sendICS = FALSE;
770     cps->sendName = appData.icsActive;
771     cps->sdKludge = FALSE;
772     cps->stKludge = FALSE;
773     TidyProgramName(cps->program, cps->host, cps->tidy);
774     cps->matchWins = 0;
775     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
776     cps->analysisSupport = 2; /* detect */
777     cps->analyzing = FALSE;
778     cps->initDone = FALSE;
779
780     /* New features added by Tord: */
781     cps->useFEN960 = FALSE;
782     cps->useOOCastle = TRUE;
783     /* End of new features added by Tord. */
784     cps->fenOverride  = appData.fenOverride[n];
785
786     /* [HGM] time odds: set factor for each machine */
787     cps->timeOdds  = appData.timeOdds[n];
788
789     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
790     cps->accumulateTC = appData.accumulateTC[n];
791     cps->maxNrOfSessions = 1;
792
793     /* [HGM] debug */
794     cps->debug = FALSE;
795     cps->supportsNPS = UNKNOWN;
796
797     /* [HGM] options */
798     cps->optionSettings  = appData.engOptions[n];
799
800     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
801     cps->isUCI = appData.isUCI[n]; /* [AS] */
802     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
803
804     if (appData.protocolVersion[n] > PROTOVER
805         || appData.protocolVersion[n] < 1)
806       {
807         char buf[MSG_SIZ];
808         int len;
809
810         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
811                        appData.protocolVersion[n]);
812         if( (len > MSG_SIZ) && appData.debugMode )
813           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
814
815         DisplayFatalError(buf, 0, 2);
816       }
817     else
818       {
819         cps->protocolVersion = appData.protocolVersion[n];
820       }
821
822     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
823 }
824
825 ChessProgramState *savCps;
826
827 void
828 LoadEngine()
829 {
830     int i;
831     if(WaitForEngine(savCps, LoadEngine)) return;
832     CommonEngineInit(); // recalculate time odds
833     if(gameInfo.variant != StringToVariant(appData.variant)) {
834         // we changed variant when loading the engine; this forces us to reset
835         Reset(TRUE, savCps != &first);
836         EditGameEvent(); // for consistency with other path, as Reset changes mode
837     }
838     InitChessProgram(savCps, FALSE);
839     SendToProgram("force\n", savCps);
840     DisplayMessage("", "");
841     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
842     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
843     ThawUI();
844     SetGNUMode();
845 }
846
847 void
848 ReplaceEngine(ChessProgramState *cps, int n)
849 {
850     EditGameEvent();
851     UnloadEngine(cps);
852     appData.noChessProgram = FALSE;
853     appData.clockMode = TRUE;
854     InitEngine(cps, n);
855     if(n) return; // only startup first engine immediately; second can wait
856     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
857     LoadEngine();
858 }
859
860 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName;
861 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
862
863 void Load(ChessProgramState *cps, int i)
864 {
865     char *p, *q, buf[MSG_SIZ];
866     if(engineLine[0]) { // an engine was selected from the combo box
867         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
868         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
869         ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
870         ParseArgsFromString(buf);
871         SwapEngines(i);
872         ReplaceEngine(cps, i);
873         return;
874     }
875     p = engineName;
876     while(q = strchr(p, SLASH)) p = q+1;
877     if(*p== NULLCHAR) return;
878     appData.chessProgram[i] = strdup(p);
879     if(engineDir[0] != NULLCHAR)
880         appData.directory[i] = engineDir;
881     else if(p != engineName) { // derive directory from engine path, when not given
882         p[-1] = 0;
883         appData.directory[i] = strdup(engineName);
884         p[-1] = '/';
885     } else appData.directory[i] = ".";
886     appData.isUCI[i] = isUCI;
887     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
888     appData.hasOwnBookUCI[i] = hasBook;
889     if(addToList) {
890         int len;
891         q = firstChessProgramNames;
892         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
893         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i], 
894                         v1 ? " -firstProtocolVersion 1" : "",
895                         hasBook ? "" : " -fNoOwnBookUCI",
896                         isUCI ? " -fUCI" : "",
897                         storeVariant ? " -variant " : "",
898                         storeVariant ? VariantName(gameInfo.variant) : "");
899 fprintf(debugFP, "new line: %s", buf);
900         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
901         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
902         if(q)   free(q);
903     }
904     ReplaceEngine(cps, i);
905 }
906
907 void
908 InitBackEnd1()
909 {
910     int matched, min, sec;
911
912     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
913     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
914
915     GetTimeMark(&programStartTime);
916     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
917     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
918
919     ClearProgramStats();
920     programStats.ok_to_send = 1;
921     programStats.seen_stat = 0;
922
923     /*
924      * Initialize game list
925      */
926     ListNew(&gameList);
927
928
929     /*
930      * Internet chess server status
931      */
932     if (appData.icsActive) {
933         appData.matchMode = FALSE;
934         appData.matchGames = 0;
935 #if ZIPPY
936         appData.noChessProgram = !appData.zippyPlay;
937 #else
938         appData.zippyPlay = FALSE;
939         appData.zippyTalk = FALSE;
940         appData.noChessProgram = TRUE;
941 #endif
942         if (*appData.icsHelper != NULLCHAR) {
943             appData.useTelnet = TRUE;
944             appData.telnetProgram = appData.icsHelper;
945         }
946     } else {
947         appData.zippyTalk = appData.zippyPlay = FALSE;
948     }
949
950     /* [AS] Initialize pv info list [HGM] and game state */
951     {
952         int i, j;
953
954         for( i=0; i<=framePtr; i++ ) {
955             pvInfoList[i].depth = -1;
956             boards[i][EP_STATUS] = EP_NONE;
957             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
958         }
959     }
960
961     /*
962      * Parse timeControl resource
963      */
964     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
965                           appData.movesPerSession)) {
966         char buf[MSG_SIZ];
967         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
968         DisplayFatalError(buf, 0, 2);
969     }
970
971     /*
972      * Parse searchTime resource
973      */
974     if (*appData.searchTime != NULLCHAR) {
975         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
976         if (matched == 1) {
977             searchTime = min * 60;
978         } else if (matched == 2) {
979             searchTime = min * 60 + sec;
980         } else {
981             char buf[MSG_SIZ];
982             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
983             DisplayFatalError(buf, 0, 2);
984         }
985     }
986
987     /* [AS] Adjudication threshold */
988     adjudicateLossThreshold = appData.adjudicateLossThreshold;
989
990     InitEngine(&first, 0);
991     InitEngine(&second, 1);
992     CommonEngineInit();
993
994     if (appData.icsActive) {
995         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
996     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
997         appData.clockMode = FALSE;
998         first.sendTime = second.sendTime = 0;
999     }
1000
1001 #if ZIPPY
1002     /* Override some settings from environment variables, for backward
1003        compatibility.  Unfortunately it's not feasible to have the env
1004        vars just set defaults, at least in xboard.  Ugh.
1005     */
1006     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1007       ZippyInit();
1008     }
1009 #endif
1010
1011     if (!appData.icsActive) {
1012       char buf[MSG_SIZ];
1013       int len;
1014
1015       /* Check for variants that are supported only in ICS mode,
1016          or not at all.  Some that are accepted here nevertheless
1017          have bugs; see comments below.
1018       */
1019       VariantClass variant = StringToVariant(appData.variant);
1020       switch (variant) {
1021       case VariantBughouse:     /* need four players and two boards */
1022       case VariantKriegspiel:   /* need to hide pieces and move details */
1023         /* case VariantFischeRandom: (Fabien: moved below) */
1024         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1025         if( (len > MSG_SIZ) && appData.debugMode )
1026           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1027
1028         DisplayFatalError(buf, 0, 2);
1029         return;
1030
1031       case VariantUnknown:
1032       case VariantLoadable:
1033       case Variant29:
1034       case Variant30:
1035       case Variant31:
1036       case Variant32:
1037       case Variant33:
1038       case Variant34:
1039       case Variant35:
1040       case Variant36:
1041       default:
1042         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1043         if( (len > MSG_SIZ) && appData.debugMode )
1044           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1045
1046         DisplayFatalError(buf, 0, 2);
1047         return;
1048
1049       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1050       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1051       case VariantGothic:     /* [HGM] should work */
1052       case VariantCapablanca: /* [HGM] should work */
1053       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1054       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1055       case VariantKnightmate: /* [HGM] should work */
1056       case VariantCylinder:   /* [HGM] untested */
1057       case VariantFalcon:     /* [HGM] untested */
1058       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1059                                  offboard interposition not understood */
1060       case VariantNormal:     /* definitely works! */
1061       case VariantWildCastle: /* pieces not automatically shuffled */
1062       case VariantNoCastle:   /* pieces not automatically shuffled */
1063       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1064       case VariantLosers:     /* should work except for win condition,
1065                                  and doesn't know captures are mandatory */
1066       case VariantSuicide:    /* should work except for win condition,
1067                                  and doesn't know captures are mandatory */
1068       case VariantGiveaway:   /* should work except for win condition,
1069                                  and doesn't know captures are mandatory */
1070       case VariantTwoKings:   /* should work */
1071       case VariantAtomic:     /* should work except for win condition */
1072       case Variant3Check:     /* should work except for win condition */
1073       case VariantShatranj:   /* should work except for all win conditions */
1074       case VariantMakruk:     /* should work except for daw countdown */
1075       case VariantBerolina:   /* might work if TestLegality is off */
1076       case VariantCapaRandom: /* should work */
1077       case VariantJanus:      /* should work */
1078       case VariantSuper:      /* experimental */
1079       case VariantGreat:      /* experimental, requires legality testing to be off */
1080       case VariantSChess:     /* S-Chess, should work */
1081       case VariantSpartan:    /* should work */
1082         break;
1083       }
1084     }
1085
1086 }
1087
1088 int NextIntegerFromString( char ** str, long * value )
1089 {
1090     int result = -1;
1091     char * s = *str;
1092
1093     while( *s == ' ' || *s == '\t' ) {
1094         s++;
1095     }
1096
1097     *value = 0;
1098
1099     if( *s >= '0' && *s <= '9' ) {
1100         while( *s >= '0' && *s <= '9' ) {
1101             *value = *value * 10 + (*s - '0');
1102             s++;
1103         }
1104
1105         result = 0;
1106     }
1107
1108     *str = s;
1109
1110     return result;
1111 }
1112
1113 int NextTimeControlFromString( char ** str, long * value )
1114 {
1115     long temp;
1116     int result = NextIntegerFromString( str, &temp );
1117
1118     if( result == 0 ) {
1119         *value = temp * 60; /* Minutes */
1120         if( **str == ':' ) {
1121             (*str)++;
1122             result = NextIntegerFromString( str, &temp );
1123             *value += temp; /* Seconds */
1124         }
1125     }
1126
1127     return result;
1128 }
1129
1130 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1131 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1132     int result = -1, type = 0; long temp, temp2;
1133
1134     if(**str != ':') return -1; // old params remain in force!
1135     (*str)++;
1136     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1137     if( NextIntegerFromString( str, &temp ) ) return -1;
1138     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1139
1140     if(**str != '/') {
1141         /* time only: incremental or sudden-death time control */
1142         if(**str == '+') { /* increment follows; read it */
1143             (*str)++;
1144             if(**str == '!') type = *(*str)++; // Bronstein TC
1145             if(result = NextIntegerFromString( str, &temp2)) return -1;
1146             *inc = temp2 * 1000;
1147             if(**str == '.') { // read fraction of increment
1148                 char *start = ++(*str);
1149                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1150                 temp2 *= 1000;
1151                 while(start++ < *str) temp2 /= 10;
1152                 *inc += temp2;
1153             }
1154         } else *inc = 0;
1155         *moves = 0; *tc = temp * 1000; *incType = type;
1156         return 0;
1157     }
1158
1159     (*str)++; /* classical time control */
1160     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1161
1162     if(result == 0) {
1163         *moves = temp;
1164         *tc    = temp2 * 1000;
1165         *inc   = 0;
1166         *incType = type;
1167     }
1168     return result;
1169 }
1170
1171 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1172 {   /* [HGM] get time to add from the multi-session time-control string */
1173     int incType, moves=1; /* kludge to force reading of first session */
1174     long time, increment;
1175     char *s = tcString;
1176
1177     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1178     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1179     do {
1180         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1181         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1182         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1183         if(movenr == -1) return time;    /* last move before new session     */
1184         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1185         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1186         if(!moves) return increment;     /* current session is incremental   */
1187         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1188     } while(movenr >= -1);               /* try again for next session       */
1189
1190     return 0; // no new time quota on this move
1191 }
1192
1193 int
1194 ParseTimeControl(tc, ti, mps)
1195      char *tc;
1196      float ti;
1197      int mps;
1198 {
1199   long tc1;
1200   long tc2;
1201   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1202   int min, sec=0;
1203
1204   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1205   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1206       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1207   if(ti > 0) {
1208
1209     if(mps)
1210       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1211     else 
1212       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1213   } else {
1214     if(mps)
1215       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1216     else 
1217       snprintf(buf, MSG_SIZ, ":%s", mytc);
1218   }
1219   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1220   
1221   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1222     return FALSE;
1223   }
1224
1225   if( *tc == '/' ) {
1226     /* Parse second time control */
1227     tc++;
1228
1229     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1230       return FALSE;
1231     }
1232
1233     if( tc2 == 0 ) {
1234       return FALSE;
1235     }
1236
1237     timeControl_2 = tc2 * 1000;
1238   }
1239   else {
1240     timeControl_2 = 0;
1241   }
1242
1243   if( tc1 == 0 ) {
1244     return FALSE;
1245   }
1246
1247   timeControl = tc1 * 1000;
1248
1249   if (ti >= 0) {
1250     timeIncrement = ti * 1000;  /* convert to ms */
1251     movesPerSession = 0;
1252   } else {
1253     timeIncrement = 0;
1254     movesPerSession = mps;
1255   }
1256   return TRUE;
1257 }
1258
1259 void
1260 InitBackEnd2()
1261 {
1262     if (appData.debugMode) {
1263         fprintf(debugFP, "%s\n", programVersion);
1264     }
1265
1266     set_cont_sequence(appData.wrapContSeq);
1267     if (appData.matchGames > 0) {
1268         appData.matchMode = TRUE;
1269     } else if (appData.matchMode) {
1270         appData.matchGames = 1;
1271     }
1272     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1273         appData.matchGames = appData.sameColorGames;
1274     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1275         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1276         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1277     }
1278     Reset(TRUE, FALSE);
1279     if (appData.noChessProgram || first.protocolVersion == 1) {
1280       InitBackEnd3();
1281     } else {
1282       /* kludge: allow timeout for initial "feature" commands */
1283       FreezeUI();
1284       DisplayMessage("", _("Starting chess program"));
1285       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1286     }
1287 }
1288
1289 int
1290 CalculateIndex(int index, int gameNr)
1291 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1292     int res;
1293     if(index > 0) return index; // fixed nmber
1294     if(index == 0) return 1;
1295     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1296     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1297     return res;
1298 }
1299
1300 int
1301 LoadGameOrPosition(int gameNr)
1302 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1303     if (*appData.loadGameFile != NULLCHAR) {
1304         if (!LoadGameFromFile(appData.loadGameFile,
1305                 CalculateIndex(appData.loadGameIndex, gameNr),
1306                               appData.loadGameFile, FALSE)) {
1307             DisplayFatalError(_("Bad game file"), 0, 1);
1308             return 0;
1309         }
1310     } else if (*appData.loadPositionFile != NULLCHAR) {
1311         if (!LoadPositionFromFile(appData.loadPositionFile,
1312                 CalculateIndex(appData.loadPositionIndex, gameNr),
1313                                   appData.loadPositionFile)) {
1314             DisplayFatalError(_("Bad position file"), 0, 1);
1315             return 0;
1316         }
1317     }
1318     return 1;
1319 }
1320
1321 void
1322 ReserveGame(int gameNr, char resChar)
1323 {
1324     FILE *tf = fopen(appData.tourneyFile, "r+");
1325     char *p, *q, c, buf[MSG_SIZ];
1326     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1327     safeStrCpy(buf, lastMsg, MSG_SIZ);
1328     DisplayMessage(_("Pick new game"), "");
1329     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1330     ParseArgsFromFile(tf);
1331     p = q = appData.results;
1332     if(appData.debugMode) {
1333       char *r = appData.participants;
1334       fprintf(debugFP, "results = '%s'\n", p);
1335       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1336       fprintf(debugFP, "\n");
1337     }
1338     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1339     nextGame = q - p;
1340     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1341     safeStrCpy(q, p, strlen(p) + 2);
1342     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1343     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1344     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1345         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1346         q[nextGame] = '*';
1347     }
1348     fseek(tf, -(strlen(p)+4), SEEK_END);
1349     c = fgetc(tf);
1350     if(c != '"') // depending on DOS or Unix line endings we can be one off
1351          fseek(tf, -(strlen(p)+2), SEEK_END);
1352     else fseek(tf, -(strlen(p)+3), SEEK_END);
1353     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1354     DisplayMessage(buf, "");
1355     free(p); appData.results = q;
1356     if(nextGame <= appData.matchGames && resChar != ' ' &&
1357        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1358         UnloadEngine(&first);  // next game belongs to other pairing;
1359         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1360     }
1361 }
1362
1363 void
1364 MatchEvent(int mode)
1365 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1366         int dummy;
1367         /* Set up machine vs. machine match */
1368         nextGame = 0;
1369         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1370         if(appData.tourneyFile[0]) {
1371             ReserveGame(-1, 0);
1372             if(nextGame > appData.matchGames) {
1373                 char buf[MSG_SIZ];
1374                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1375                 DisplayError(buf, 0);
1376                 appData.tourneyFile[0] = 0;
1377                 return;
1378             }
1379         } else
1380         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1381             DisplayFatalError(_("Can't have a match with no chess programs"),
1382                               0, 2);
1383             return;
1384         }
1385         matchMode = mode;
1386         matchGame = roundNr = 1;
1387         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1388         NextMatchGame();
1389 }
1390
1391 void
1392 InitBackEnd3 P((void))
1393 {
1394     GameMode initialMode;
1395     char buf[MSG_SIZ];
1396     int err, len;
1397
1398     InitChessProgram(&first, startedFromSetupPosition);
1399
1400     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1401         free(programVersion);
1402         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1403         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1404     }
1405
1406     if (appData.icsActive) {
1407 #ifdef WIN32
1408         /* [DM] Make a console window if needed [HGM] merged ifs */
1409         ConsoleCreate();
1410 #endif
1411         err = establish();
1412         if (err != 0)
1413           {
1414             if (*appData.icsCommPort != NULLCHAR)
1415               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1416                              appData.icsCommPort);
1417             else
1418               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1419                         appData.icsHost, appData.icsPort);
1420
1421             if( (len > MSG_SIZ) && appData.debugMode )
1422               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1423
1424             DisplayFatalError(buf, err, 1);
1425             return;
1426         }
1427         SetICSMode();
1428         telnetISR =
1429           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1430         fromUserISR =
1431           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1432         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1433             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1434     } else if (appData.noChessProgram) {
1435         SetNCPMode();
1436     } else {
1437         SetGNUMode();
1438     }
1439
1440     if (*appData.cmailGameName != NULLCHAR) {
1441         SetCmailMode();
1442         OpenLoopback(&cmailPR);
1443         cmailISR =
1444           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1445     }
1446
1447     ThawUI();
1448     DisplayMessage("", "");
1449     if (StrCaseCmp(appData.initialMode, "") == 0) {
1450       initialMode = BeginningOfGame;
1451       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1452         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1453         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1454         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1455         ModeHighlight();
1456       }
1457     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1458       initialMode = TwoMachinesPlay;
1459     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1460       initialMode = AnalyzeFile;
1461     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1462       initialMode = AnalyzeMode;
1463     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1464       initialMode = MachinePlaysWhite;
1465     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1466       initialMode = MachinePlaysBlack;
1467     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1468       initialMode = EditGame;
1469     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1470       initialMode = EditPosition;
1471     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1472       initialMode = Training;
1473     } else {
1474       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1475       if( (len > MSG_SIZ) && appData.debugMode )
1476         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1477
1478       DisplayFatalError(buf, 0, 2);
1479       return;
1480     }
1481
1482     if (appData.matchMode) {
1483         if(appData.tourneyFile[0]) { // start tourney from command line
1484             FILE *f;
1485             if(f = fopen(appData.tourneyFile, "r")) {
1486                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1487                 fclose(f);
1488             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1489         }
1490         MatchEvent(TRUE);
1491     } else if (*appData.cmailGameName != NULLCHAR) {
1492         /* Set up cmail mode */
1493         ReloadCmailMsgEvent(TRUE);
1494     } else {
1495         /* Set up other modes */
1496         if (initialMode == AnalyzeFile) {
1497           if (*appData.loadGameFile == NULLCHAR) {
1498             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1499             return;
1500           }
1501         }
1502         if (*appData.loadGameFile != NULLCHAR) {
1503             (void) LoadGameFromFile(appData.loadGameFile,
1504                                     appData.loadGameIndex,
1505                                     appData.loadGameFile, TRUE);
1506         } else if (*appData.loadPositionFile != NULLCHAR) {
1507             (void) LoadPositionFromFile(appData.loadPositionFile,
1508                                         appData.loadPositionIndex,
1509                                         appData.loadPositionFile);
1510             /* [HGM] try to make self-starting even after FEN load */
1511             /* to allow automatic setup of fairy variants with wtm */
1512             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1513                 gameMode = BeginningOfGame;
1514                 setboardSpoiledMachineBlack = 1;
1515             }
1516             /* [HGM] loadPos: make that every new game uses the setup */
1517             /* from file as long as we do not switch variant          */
1518             if(!blackPlaysFirst) {
1519                 startedFromPositionFile = TRUE;
1520                 CopyBoard(filePosition, boards[0]);
1521             }
1522         }
1523         if (initialMode == AnalyzeMode) {
1524           if (appData.noChessProgram) {
1525             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1526             return;
1527           }
1528           if (appData.icsActive) {
1529             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1530             return;
1531           }
1532           AnalyzeModeEvent();
1533         } else if (initialMode == AnalyzeFile) {
1534           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1535           ShowThinkingEvent();
1536           AnalyzeFileEvent();
1537           AnalysisPeriodicEvent(1);
1538         } else if (initialMode == MachinePlaysWhite) {
1539           if (appData.noChessProgram) {
1540             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1541                               0, 2);
1542             return;
1543           }
1544           if (appData.icsActive) {
1545             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1546                               0, 2);
1547             return;
1548           }
1549           MachineWhiteEvent();
1550         } else if (initialMode == MachinePlaysBlack) {
1551           if (appData.noChessProgram) {
1552             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1553                               0, 2);
1554             return;
1555           }
1556           if (appData.icsActive) {
1557             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1558                               0, 2);
1559             return;
1560           }
1561           MachineBlackEvent();
1562         } else if (initialMode == TwoMachinesPlay) {
1563           if (appData.noChessProgram) {
1564             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1565                               0, 2);
1566             return;
1567           }
1568           if (appData.icsActive) {
1569             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1570                               0, 2);
1571             return;
1572           }
1573           TwoMachinesEvent();
1574         } else if (initialMode == EditGame) {
1575           EditGameEvent();
1576         } else if (initialMode == EditPosition) {
1577           EditPositionEvent();
1578         } else if (initialMode == Training) {
1579           if (*appData.loadGameFile == NULLCHAR) {
1580             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1581             return;
1582           }
1583           TrainingEvent();
1584         }
1585     }
1586 }
1587
1588 /*
1589  * Establish will establish a contact to a remote host.port.
1590  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1591  *  used to talk to the host.
1592  * Returns 0 if okay, error code if not.
1593  */
1594 int
1595 establish()
1596 {
1597     char buf[MSG_SIZ];
1598
1599     if (*appData.icsCommPort != NULLCHAR) {
1600         /* Talk to the host through a serial comm port */
1601         return OpenCommPort(appData.icsCommPort, &icsPR);
1602
1603     } else if (*appData.gateway != NULLCHAR) {
1604         if (*appData.remoteShell == NULLCHAR) {
1605             /* Use the rcmd protocol to run telnet program on a gateway host */
1606             snprintf(buf, sizeof(buf), "%s %s %s",
1607                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1608             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1609
1610         } else {
1611             /* Use the rsh program to run telnet program on a gateway host */
1612             if (*appData.remoteUser == NULLCHAR) {
1613                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1614                         appData.gateway, appData.telnetProgram,
1615                         appData.icsHost, appData.icsPort);
1616             } else {
1617                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1618                         appData.remoteShell, appData.gateway,
1619                         appData.remoteUser, appData.telnetProgram,
1620                         appData.icsHost, appData.icsPort);
1621             }
1622             return StartChildProcess(buf, "", &icsPR);
1623
1624         }
1625     } else if (appData.useTelnet) {
1626         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1627
1628     } else {
1629         /* TCP socket interface differs somewhat between
1630            Unix and NT; handle details in the front end.
1631            */
1632         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1633     }
1634 }
1635
1636 void EscapeExpand(char *p, char *q)
1637 {       // [HGM] initstring: routine to shape up string arguments
1638         while(*p++ = *q++) if(p[-1] == '\\')
1639             switch(*q++) {
1640                 case 'n': p[-1] = '\n'; break;
1641                 case 'r': p[-1] = '\r'; break;
1642                 case 't': p[-1] = '\t'; break;
1643                 case '\\': p[-1] = '\\'; break;
1644                 case 0: *p = 0; return;
1645                 default: p[-1] = q[-1]; break;
1646             }
1647 }
1648
1649 void
1650 show_bytes(fp, buf, count)
1651      FILE *fp;
1652      char *buf;
1653      int count;
1654 {
1655     while (count--) {
1656         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1657             fprintf(fp, "\\%03o", *buf & 0xff);
1658         } else {
1659             putc(*buf, fp);
1660         }
1661         buf++;
1662     }
1663     fflush(fp);
1664 }
1665
1666 /* Returns an errno value */
1667 int
1668 OutputMaybeTelnet(pr, message, count, outError)
1669      ProcRef pr;
1670      char *message;
1671      int count;
1672      int *outError;
1673 {
1674     char buf[8192], *p, *q, *buflim;
1675     int left, newcount, outcount;
1676
1677     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1678         *appData.gateway != NULLCHAR) {
1679         if (appData.debugMode) {
1680             fprintf(debugFP, ">ICS: ");
1681             show_bytes(debugFP, message, count);
1682             fprintf(debugFP, "\n");
1683         }
1684         return OutputToProcess(pr, message, count, outError);
1685     }
1686
1687     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1688     p = message;
1689     q = buf;
1690     left = count;
1691     newcount = 0;
1692     while (left) {
1693         if (q >= buflim) {
1694             if (appData.debugMode) {
1695                 fprintf(debugFP, ">ICS: ");
1696                 show_bytes(debugFP, buf, newcount);
1697                 fprintf(debugFP, "\n");
1698             }
1699             outcount = OutputToProcess(pr, buf, newcount, outError);
1700             if (outcount < newcount) return -1; /* to be sure */
1701             q = buf;
1702             newcount = 0;
1703         }
1704         if (*p == '\n') {
1705             *q++ = '\r';
1706             newcount++;
1707         } else if (((unsigned char) *p) == TN_IAC) {
1708             *q++ = (char) TN_IAC;
1709             newcount ++;
1710         }
1711         *q++ = *p++;
1712         newcount++;
1713         left--;
1714     }
1715     if (appData.debugMode) {
1716         fprintf(debugFP, ">ICS: ");
1717         show_bytes(debugFP, buf, newcount);
1718         fprintf(debugFP, "\n");
1719     }
1720     outcount = OutputToProcess(pr, buf, newcount, outError);
1721     if (outcount < newcount) return -1; /* to be sure */
1722     return count;
1723 }
1724
1725 void
1726 read_from_player(isr, closure, message, count, error)
1727      InputSourceRef isr;
1728      VOIDSTAR closure;
1729      char *message;
1730      int count;
1731      int error;
1732 {
1733     int outError, outCount;
1734     static int gotEof = 0;
1735
1736     /* Pass data read from player on to ICS */
1737     if (count > 0) {
1738         gotEof = 0;
1739         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1740         if (outCount < count) {
1741             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1742         }
1743     } else if (count < 0) {
1744         RemoveInputSource(isr);
1745         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1746     } else if (gotEof++ > 0) {
1747         RemoveInputSource(isr);
1748         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1749     }
1750 }
1751
1752 void
1753 KeepAlive()
1754 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1755     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1756     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1757     SendToICS("date\n");
1758     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1759 }
1760
1761 /* added routine for printf style output to ics */
1762 void ics_printf(char *format, ...)
1763 {
1764     char buffer[MSG_SIZ];
1765     va_list args;
1766
1767     va_start(args, format);
1768     vsnprintf(buffer, sizeof(buffer), format, args);
1769     buffer[sizeof(buffer)-1] = '\0';
1770     SendToICS(buffer);
1771     va_end(args);
1772 }
1773
1774 void
1775 SendToICS(s)
1776      char *s;
1777 {
1778     int count, outCount, outError;
1779
1780     if (icsPR == NULL) return;
1781
1782     count = strlen(s);
1783     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1784     if (outCount < count) {
1785         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1786     }
1787 }
1788
1789 /* This is used for sending logon scripts to the ICS. Sending
1790    without a delay causes problems when using timestamp on ICC
1791    (at least on my machine). */
1792 void
1793 SendToICSDelayed(s,msdelay)
1794      char *s;
1795      long msdelay;
1796 {
1797     int count, outCount, outError;
1798
1799     if (icsPR == NULL) return;
1800
1801     count = strlen(s);
1802     if (appData.debugMode) {
1803         fprintf(debugFP, ">ICS: ");
1804         show_bytes(debugFP, s, count);
1805         fprintf(debugFP, "\n");
1806     }
1807     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1808                                       msdelay);
1809     if (outCount < count) {
1810         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1811     }
1812 }
1813
1814
1815 /* Remove all highlighting escape sequences in s
1816    Also deletes any suffix starting with '('
1817    */
1818 char *
1819 StripHighlightAndTitle(s)
1820      char *s;
1821 {
1822     static char retbuf[MSG_SIZ];
1823     char *p = retbuf;
1824
1825     while (*s != NULLCHAR) {
1826         while (*s == '\033') {
1827             while (*s != NULLCHAR && !isalpha(*s)) s++;
1828             if (*s != NULLCHAR) s++;
1829         }
1830         while (*s != NULLCHAR && *s != '\033') {
1831             if (*s == '(' || *s == '[') {
1832                 *p = NULLCHAR;
1833                 return retbuf;
1834             }
1835             *p++ = *s++;
1836         }
1837     }
1838     *p = NULLCHAR;
1839     return retbuf;
1840 }
1841
1842 /* Remove all highlighting escape sequences in s */
1843 char *
1844 StripHighlight(s)
1845      char *s;
1846 {
1847     static char retbuf[MSG_SIZ];
1848     char *p = retbuf;
1849
1850     while (*s != NULLCHAR) {
1851         while (*s == '\033') {
1852             while (*s != NULLCHAR && !isalpha(*s)) s++;
1853             if (*s != NULLCHAR) s++;
1854         }
1855         while (*s != NULLCHAR && *s != '\033') {
1856             *p++ = *s++;
1857         }
1858     }
1859     *p = NULLCHAR;
1860     return retbuf;
1861 }
1862
1863 char *variantNames[] = VARIANT_NAMES;
1864 char *
1865 VariantName(v)
1866      VariantClass v;
1867 {
1868     return variantNames[v];
1869 }
1870
1871
1872 /* Identify a variant from the strings the chess servers use or the
1873    PGN Variant tag names we use. */
1874 VariantClass
1875 StringToVariant(e)
1876      char *e;
1877 {
1878     char *p;
1879     int wnum = -1;
1880     VariantClass v = VariantNormal;
1881     int i, found = FALSE;
1882     char buf[MSG_SIZ];
1883     int len;
1884
1885     if (!e) return v;
1886
1887     /* [HGM] skip over optional board-size prefixes */
1888     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1889         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1890         while( *e++ != '_');
1891     }
1892
1893     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1894         v = VariantNormal;
1895         found = TRUE;
1896     } else
1897     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1898       if (StrCaseStr(e, variantNames[i])) {
1899         v = (VariantClass) i;
1900         found = TRUE;
1901         break;
1902       }
1903     }
1904
1905     if (!found) {
1906       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1907           || StrCaseStr(e, "wild/fr")
1908           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1909         v = VariantFischeRandom;
1910       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1911                  (i = 1, p = StrCaseStr(e, "w"))) {
1912         p += i;
1913         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1914         if (isdigit(*p)) {
1915           wnum = atoi(p);
1916         } else {
1917           wnum = -1;
1918         }
1919         switch (wnum) {
1920         case 0: /* FICS only, actually */
1921         case 1:
1922           /* Castling legal even if K starts on d-file */
1923           v = VariantWildCastle;
1924           break;
1925         case 2:
1926         case 3:
1927         case 4:
1928           /* Castling illegal even if K & R happen to start in
1929              normal positions. */
1930           v = VariantNoCastle;
1931           break;
1932         case 5:
1933         case 7:
1934         case 8:
1935         case 10:
1936         case 11:
1937         case 12:
1938         case 13:
1939         case 14:
1940         case 15:
1941         case 18:
1942         case 19:
1943           /* Castling legal iff K & R start in normal positions */
1944           v = VariantNormal;
1945           break;
1946         case 6:
1947         case 20:
1948         case 21:
1949           /* Special wilds for position setup; unclear what to do here */
1950           v = VariantLoadable;
1951           break;
1952         case 9:
1953           /* Bizarre ICC game */
1954           v = VariantTwoKings;
1955           break;
1956         case 16:
1957           v = VariantKriegspiel;
1958           break;
1959         case 17:
1960           v = VariantLosers;
1961           break;
1962         case 22:
1963           v = VariantFischeRandom;
1964           break;
1965         case 23:
1966           v = VariantCrazyhouse;
1967           break;
1968         case 24:
1969           v = VariantBughouse;
1970           break;
1971         case 25:
1972           v = Variant3Check;
1973           break;
1974         case 26:
1975           /* Not quite the same as FICS suicide! */
1976           v = VariantGiveaway;
1977           break;
1978         case 27:
1979           v = VariantAtomic;
1980           break;
1981         case 28:
1982           v = VariantShatranj;
1983           break;
1984
1985         /* Temporary names for future ICC types.  The name *will* change in
1986            the next xboard/WinBoard release after ICC defines it. */
1987         case 29:
1988           v = Variant29;
1989           break;
1990         case 30:
1991           v = Variant30;
1992           break;
1993         case 31:
1994           v = Variant31;
1995           break;
1996         case 32:
1997           v = Variant32;
1998           break;
1999         case 33:
2000           v = Variant33;
2001           break;
2002         case 34:
2003           v = Variant34;
2004           break;
2005         case 35:
2006           v = Variant35;
2007           break;
2008         case 36:
2009           v = Variant36;
2010           break;
2011         case 37:
2012           v = VariantShogi;
2013           break;
2014         case 38:
2015           v = VariantXiangqi;
2016           break;
2017         case 39:
2018           v = VariantCourier;
2019           break;
2020         case 40:
2021           v = VariantGothic;
2022           break;
2023         case 41:
2024           v = VariantCapablanca;
2025           break;
2026         case 42:
2027           v = VariantKnightmate;
2028           break;
2029         case 43:
2030           v = VariantFairy;
2031           break;
2032         case 44:
2033           v = VariantCylinder;
2034           break;
2035         case 45:
2036           v = VariantFalcon;
2037           break;
2038         case 46:
2039           v = VariantCapaRandom;
2040           break;
2041         case 47:
2042           v = VariantBerolina;
2043           break;
2044         case 48:
2045           v = VariantJanus;
2046           break;
2047         case 49:
2048           v = VariantSuper;
2049           break;
2050         case 50:
2051           v = VariantGreat;
2052           break;
2053         case -1:
2054           /* Found "wild" or "w" in the string but no number;
2055              must assume it's normal chess. */
2056           v = VariantNormal;
2057           break;
2058         default:
2059           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2060           if( (len > MSG_SIZ) && appData.debugMode )
2061             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2062
2063           DisplayError(buf, 0);
2064           v = VariantUnknown;
2065           break;
2066         }
2067       }
2068     }
2069     if (appData.debugMode) {
2070       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2071               e, wnum, VariantName(v));
2072     }
2073     return v;
2074 }
2075
2076 static int leftover_start = 0, leftover_len = 0;
2077 char star_match[STAR_MATCH_N][MSG_SIZ];
2078
2079 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2080    advance *index beyond it, and set leftover_start to the new value of
2081    *index; else return FALSE.  If pattern contains the character '*', it
2082    matches any sequence of characters not containing '\r', '\n', or the
2083    character following the '*' (if any), and the matched sequence(s) are
2084    copied into star_match.
2085    */
2086 int
2087 looking_at(buf, index, pattern)
2088      char *buf;
2089      int *index;
2090      char *pattern;
2091 {
2092     char *bufp = &buf[*index], *patternp = pattern;
2093     int star_count = 0;
2094     char *matchp = star_match[0];
2095
2096     for (;;) {
2097         if (*patternp == NULLCHAR) {
2098             *index = leftover_start = bufp - buf;
2099             *matchp = NULLCHAR;
2100             return TRUE;
2101         }
2102         if (*bufp == NULLCHAR) return FALSE;
2103         if (*patternp == '*') {
2104             if (*bufp == *(patternp + 1)) {
2105                 *matchp = NULLCHAR;
2106                 matchp = star_match[++star_count];
2107                 patternp += 2;
2108                 bufp++;
2109                 continue;
2110             } else if (*bufp == '\n' || *bufp == '\r') {
2111                 patternp++;
2112                 if (*patternp == NULLCHAR)
2113                   continue;
2114                 else
2115                   return FALSE;
2116             } else {
2117                 *matchp++ = *bufp++;
2118                 continue;
2119             }
2120         }
2121         if (*patternp != *bufp) return FALSE;
2122         patternp++;
2123         bufp++;
2124     }
2125 }
2126
2127 void
2128 SendToPlayer(data, length)
2129      char *data;
2130      int length;
2131 {
2132     int error, outCount;
2133     outCount = OutputToProcess(NoProc, data, length, &error);
2134     if (outCount < length) {
2135         DisplayFatalError(_("Error writing to display"), error, 1);
2136     }
2137 }
2138
2139 void
2140 PackHolding(packed, holding)
2141      char packed[];
2142      char *holding;
2143 {
2144     char *p = holding;
2145     char *q = packed;
2146     int runlength = 0;
2147     int curr = 9999;
2148     do {
2149         if (*p == curr) {
2150             runlength++;
2151         } else {
2152             switch (runlength) {
2153               case 0:
2154                 break;
2155               case 1:
2156                 *q++ = curr;
2157                 break;
2158               case 2:
2159                 *q++ = curr;
2160                 *q++ = curr;
2161                 break;
2162               default:
2163                 sprintf(q, "%d", runlength);
2164                 while (*q) q++;
2165                 *q++ = curr;
2166                 break;
2167             }
2168             runlength = 1;
2169             curr = *p;
2170         }
2171     } while (*p++);
2172     *q = NULLCHAR;
2173 }
2174
2175 /* Telnet protocol requests from the front end */
2176 void
2177 TelnetRequest(ddww, option)
2178      unsigned char ddww, option;
2179 {
2180     unsigned char msg[3];
2181     int outCount, outError;
2182
2183     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2184
2185     if (appData.debugMode) {
2186         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2187         switch (ddww) {
2188           case TN_DO:
2189             ddwwStr = "DO";
2190             break;
2191           case TN_DONT:
2192             ddwwStr = "DONT";
2193             break;
2194           case TN_WILL:
2195             ddwwStr = "WILL";
2196             break;
2197           case TN_WONT:
2198             ddwwStr = "WONT";
2199             break;
2200           default:
2201             ddwwStr = buf1;
2202             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2203             break;
2204         }
2205         switch (option) {
2206           case TN_ECHO:
2207             optionStr = "ECHO";
2208             break;
2209           default:
2210             optionStr = buf2;
2211             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2212             break;
2213         }
2214         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2215     }
2216     msg[0] = TN_IAC;
2217     msg[1] = ddww;
2218     msg[2] = option;
2219     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2220     if (outCount < 3) {
2221         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2222     }
2223 }
2224
2225 void
2226 DoEcho()
2227 {
2228     if (!appData.icsActive) return;
2229     TelnetRequest(TN_DO, TN_ECHO);
2230 }
2231
2232 void
2233 DontEcho()
2234 {
2235     if (!appData.icsActive) return;
2236     TelnetRequest(TN_DONT, TN_ECHO);
2237 }
2238
2239 void
2240 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2241 {
2242     /* put the holdings sent to us by the server on the board holdings area */
2243     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2244     char p;
2245     ChessSquare piece;
2246
2247     if(gameInfo.holdingsWidth < 2)  return;
2248     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2249         return; // prevent overwriting by pre-board holdings
2250
2251     if( (int)lowestPiece >= BlackPawn ) {
2252         holdingsColumn = 0;
2253         countsColumn = 1;
2254         holdingsStartRow = BOARD_HEIGHT-1;
2255         direction = -1;
2256     } else {
2257         holdingsColumn = BOARD_WIDTH-1;
2258         countsColumn = BOARD_WIDTH-2;
2259         holdingsStartRow = 0;
2260         direction = 1;
2261     }
2262
2263     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2264         board[i][holdingsColumn] = EmptySquare;
2265         board[i][countsColumn]   = (ChessSquare) 0;
2266     }
2267     while( (p=*holdings++) != NULLCHAR ) {
2268         piece = CharToPiece( ToUpper(p) );
2269         if(piece == EmptySquare) continue;
2270         /*j = (int) piece - (int) WhitePawn;*/
2271         j = PieceToNumber(piece);
2272         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2273         if(j < 0) continue;               /* should not happen */
2274         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2275         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2276         board[holdingsStartRow+j*direction][countsColumn]++;
2277     }
2278 }
2279
2280
2281 void
2282 VariantSwitch(Board board, VariantClass newVariant)
2283 {
2284    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2285    static Board oldBoard;
2286
2287    startedFromPositionFile = FALSE;
2288    if(gameInfo.variant == newVariant) return;
2289
2290    /* [HGM] This routine is called each time an assignment is made to
2291     * gameInfo.variant during a game, to make sure the board sizes
2292     * are set to match the new variant. If that means adding or deleting
2293     * holdings, we shift the playing board accordingly
2294     * This kludge is needed because in ICS observe mode, we get boards
2295     * of an ongoing game without knowing the variant, and learn about the
2296     * latter only later. This can be because of the move list we requested,
2297     * in which case the game history is refilled from the beginning anyway,
2298     * but also when receiving holdings of a crazyhouse game. In the latter
2299     * case we want to add those holdings to the already received position.
2300     */
2301
2302
2303    if (appData.debugMode) {
2304      fprintf(debugFP, "Switch board from %s to %s\n",
2305              VariantName(gameInfo.variant), VariantName(newVariant));
2306      setbuf(debugFP, NULL);
2307    }
2308    shuffleOpenings = 0;       /* [HGM] shuffle */
2309    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2310    switch(newVariant)
2311      {
2312      case VariantShogi:
2313        newWidth = 9;  newHeight = 9;
2314        gameInfo.holdingsSize = 7;
2315      case VariantBughouse:
2316      case VariantCrazyhouse:
2317        newHoldingsWidth = 2; break;
2318      case VariantGreat:
2319        newWidth = 10;
2320      case VariantSuper:
2321        newHoldingsWidth = 2;
2322        gameInfo.holdingsSize = 8;
2323        break;
2324      case VariantGothic:
2325      case VariantCapablanca:
2326      case VariantCapaRandom:
2327        newWidth = 10;
2328      default:
2329        newHoldingsWidth = gameInfo.holdingsSize = 0;
2330      };
2331
2332    if(newWidth  != gameInfo.boardWidth  ||
2333       newHeight != gameInfo.boardHeight ||
2334       newHoldingsWidth != gameInfo.holdingsWidth ) {
2335
2336      /* shift position to new playing area, if needed */
2337      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2338        for(i=0; i<BOARD_HEIGHT; i++)
2339          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2340            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2341              board[i][j];
2342        for(i=0; i<newHeight; i++) {
2343          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2344          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2345        }
2346      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2347        for(i=0; i<BOARD_HEIGHT; i++)
2348          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2349            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2350              board[i][j];
2351      }
2352      gameInfo.boardWidth  = newWidth;
2353      gameInfo.boardHeight = newHeight;
2354      gameInfo.holdingsWidth = newHoldingsWidth;
2355      gameInfo.variant = newVariant;
2356      InitDrawingSizes(-2, 0);
2357    } else gameInfo.variant = newVariant;
2358    CopyBoard(oldBoard, board);   // remember correctly formatted board
2359      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2360    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2361 }
2362
2363 static int loggedOn = FALSE;
2364
2365 /*-- Game start info cache: --*/
2366 int gs_gamenum;
2367 char gs_kind[MSG_SIZ];
2368 static char player1Name[128] = "";
2369 static char player2Name[128] = "";
2370 static char cont_seq[] = "\n\\   ";
2371 static int player1Rating = -1;
2372 static int player2Rating = -1;
2373 /*----------------------------*/
2374
2375 ColorClass curColor = ColorNormal;
2376 int suppressKibitz = 0;
2377
2378 // [HGM] seekgraph
2379 Boolean soughtPending = FALSE;
2380 Boolean seekGraphUp;
2381 #define MAX_SEEK_ADS 200
2382 #define SQUARE 0x80
2383 char *seekAdList[MAX_SEEK_ADS];
2384 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2385 float tcList[MAX_SEEK_ADS];
2386 char colorList[MAX_SEEK_ADS];
2387 int nrOfSeekAds = 0;
2388 int minRating = 1010, maxRating = 2800;
2389 int hMargin = 10, vMargin = 20, h, w;
2390 extern int squareSize, lineGap;
2391
2392 void
2393 PlotSeekAd(int i)
2394 {
2395         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2396         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2397         if(r < minRating+100 && r >=0 ) r = minRating+100;
2398         if(r > maxRating) r = maxRating;
2399         if(tc < 1.) tc = 1.;
2400         if(tc > 95.) tc = 95.;
2401         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2402         y = ((double)r - minRating)/(maxRating - minRating)
2403             * (h-vMargin-squareSize/8-1) + vMargin;
2404         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2405         if(strstr(seekAdList[i], " u ")) color = 1;
2406         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2407            !strstr(seekAdList[i], "bullet") &&
2408            !strstr(seekAdList[i], "blitz") &&
2409            !strstr(seekAdList[i], "standard") ) color = 2;
2410         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2411         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2412 }
2413
2414 void
2415 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2416 {
2417         char buf[MSG_SIZ], *ext = "";
2418         VariantClass v = StringToVariant(type);
2419         if(strstr(type, "wild")) {
2420             ext = type + 4; // append wild number
2421             if(v == VariantFischeRandom) type = "chess960"; else
2422             if(v == VariantLoadable) type = "setup"; else
2423             type = VariantName(v);
2424         }
2425         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2426         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2427             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2428             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2429             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2430             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2431             seekNrList[nrOfSeekAds] = nr;
2432             zList[nrOfSeekAds] = 0;
2433             seekAdList[nrOfSeekAds++] = StrSave(buf);
2434             if(plot) PlotSeekAd(nrOfSeekAds-1);
2435         }
2436 }
2437
2438 void
2439 EraseSeekDot(int i)
2440 {
2441     int x = xList[i], y = yList[i], d=squareSize/4, k;
2442     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2443     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2444     // now replot every dot that overlapped
2445     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2446         int xx = xList[k], yy = yList[k];
2447         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2448             DrawSeekDot(xx, yy, colorList[k]);
2449     }
2450 }
2451
2452 void
2453 RemoveSeekAd(int nr)
2454 {
2455         int i;
2456         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2457             EraseSeekDot(i);
2458             if(seekAdList[i]) free(seekAdList[i]);
2459             seekAdList[i] = seekAdList[--nrOfSeekAds];
2460             seekNrList[i] = seekNrList[nrOfSeekAds];
2461             ratingList[i] = ratingList[nrOfSeekAds];
2462             colorList[i]  = colorList[nrOfSeekAds];
2463             tcList[i] = tcList[nrOfSeekAds];
2464             xList[i]  = xList[nrOfSeekAds];
2465             yList[i]  = yList[nrOfSeekAds];
2466             zList[i]  = zList[nrOfSeekAds];
2467             seekAdList[nrOfSeekAds] = NULL;
2468             break;
2469         }
2470 }
2471
2472 Boolean
2473 MatchSoughtLine(char *line)
2474 {
2475     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2476     int nr, base, inc, u=0; char dummy;
2477
2478     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2479        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2480        (u=1) &&
2481        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2482         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2483         // match: compact and save the line
2484         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2485         return TRUE;
2486     }
2487     return FALSE;
2488 }
2489
2490 int
2491 DrawSeekGraph()
2492 {
2493     int i;
2494     if(!seekGraphUp) return FALSE;
2495     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2496     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2497
2498     DrawSeekBackground(0, 0, w, h);
2499     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2500     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2501     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2502         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2503         yy = h-1-yy;
2504         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2505         if(i%500 == 0) {
2506             char buf[MSG_SIZ];
2507             snprintf(buf, MSG_SIZ, "%d", i);
2508             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2509         }
2510     }
2511     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2512     for(i=1; i<100; i+=(i<10?1:5)) {
2513         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2514         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2515         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2516             char buf[MSG_SIZ];
2517             snprintf(buf, MSG_SIZ, "%d", i);
2518             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2519         }
2520     }
2521     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2522     return TRUE;
2523 }
2524
2525 int SeekGraphClick(ClickType click, int x, int y, int moving)
2526 {
2527     static int lastDown = 0, displayed = 0, lastSecond;
2528     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2529         if(click == Release || moving) return FALSE;
2530         nrOfSeekAds = 0;
2531         soughtPending = TRUE;
2532         SendToICS(ics_prefix);
2533         SendToICS("sought\n"); // should this be "sought all"?
2534     } else { // issue challenge based on clicked ad
2535         int dist = 10000; int i, closest = 0, second = 0;
2536         for(i=0; i<nrOfSeekAds; i++) {
2537             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2538             if(d < dist) { dist = d; closest = i; }
2539             second += (d - zList[i] < 120); // count in-range ads
2540             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2541         }
2542         if(dist < 120) {
2543             char buf[MSG_SIZ];
2544             second = (second > 1);
2545             if(displayed != closest || second != lastSecond) {
2546                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2547                 lastSecond = second; displayed = closest;
2548             }
2549             if(click == Press) {
2550                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2551                 lastDown = closest;
2552                 return TRUE;
2553             } // on press 'hit', only show info
2554             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2555             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2556             SendToICS(ics_prefix);
2557             SendToICS(buf);
2558             return TRUE; // let incoming board of started game pop down the graph
2559         } else if(click == Release) { // release 'miss' is ignored
2560             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2561             if(moving == 2) { // right up-click
2562                 nrOfSeekAds = 0; // refresh graph
2563                 soughtPending = TRUE;
2564                 SendToICS(ics_prefix);
2565                 SendToICS("sought\n"); // should this be "sought all"?
2566             }
2567             return TRUE;
2568         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2569         // press miss or release hit 'pop down' seek graph
2570         seekGraphUp = FALSE;
2571         DrawPosition(TRUE, NULL);
2572     }
2573     return TRUE;
2574 }
2575
2576 void
2577 read_from_ics(isr, closure, data, count, error)
2578      InputSourceRef isr;
2579      VOIDSTAR closure;
2580      char *data;
2581      int count;
2582      int error;
2583 {
2584 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2585 #define STARTED_NONE 0
2586 #define STARTED_MOVES 1
2587 #define STARTED_BOARD 2
2588 #define STARTED_OBSERVE 3
2589 #define STARTED_HOLDINGS 4
2590 #define STARTED_CHATTER 5
2591 #define STARTED_COMMENT 6
2592 #define STARTED_MOVES_NOHIDE 7
2593
2594     static int started = STARTED_NONE;
2595     static char parse[20000];
2596     static int parse_pos = 0;
2597     static char buf[BUF_SIZE + 1];
2598     static int firstTime = TRUE, intfSet = FALSE;
2599     static ColorClass prevColor = ColorNormal;
2600     static int savingComment = FALSE;
2601     static int cmatch = 0; // continuation sequence match
2602     char *bp;
2603     char str[MSG_SIZ];
2604     int i, oldi;
2605     int buf_len;
2606     int next_out;
2607     int tkind;
2608     int backup;    /* [DM] For zippy color lines */
2609     char *p;
2610     char talker[MSG_SIZ]; // [HGM] chat
2611     int channel;
2612
2613     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2614
2615     if (appData.debugMode) {
2616       if (!error) {
2617         fprintf(debugFP, "<ICS: ");
2618         show_bytes(debugFP, data, count);
2619         fprintf(debugFP, "\n");
2620       }
2621     }
2622
2623     if (appData.debugMode) { int f = forwardMostMove;
2624         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2625                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2626                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2627     }
2628     if (count > 0) {
2629         /* If last read ended with a partial line that we couldn't parse,
2630            prepend it to the new read and try again. */
2631         if (leftover_len > 0) {
2632             for (i=0; i<leftover_len; i++)
2633               buf[i] = buf[leftover_start + i];
2634         }
2635
2636     /* copy new characters into the buffer */
2637     bp = buf + leftover_len;
2638     buf_len=leftover_len;
2639     for (i=0; i<count; i++)
2640     {
2641         // ignore these
2642         if (data[i] == '\r')
2643             continue;
2644
2645         // join lines split by ICS?
2646         if (!appData.noJoin)
2647         {
2648             /*
2649                 Joining just consists of finding matches against the
2650                 continuation sequence, and discarding that sequence
2651                 if found instead of copying it.  So, until a match
2652                 fails, there's nothing to do since it might be the
2653                 complete sequence, and thus, something we don't want
2654                 copied.
2655             */
2656             if (data[i] == cont_seq[cmatch])
2657             {
2658                 cmatch++;
2659                 if (cmatch == strlen(cont_seq))
2660                 {
2661                     cmatch = 0; // complete match.  just reset the counter
2662
2663                     /*
2664                         it's possible for the ICS to not include the space
2665                         at the end of the last word, making our [correct]
2666                         join operation fuse two separate words.  the server
2667                         does this when the space occurs at the width setting.
2668                     */
2669                     if (!buf_len || buf[buf_len-1] != ' ')
2670                     {
2671                         *bp++ = ' ';
2672                         buf_len++;
2673                     }
2674                 }
2675                 continue;
2676             }
2677             else if (cmatch)
2678             {
2679                 /*
2680                     match failed, so we have to copy what matched before
2681                     falling through and copying this character.  In reality,
2682                     this will only ever be just the newline character, but
2683                     it doesn't hurt to be precise.
2684                 */
2685                 strncpy(bp, cont_seq, cmatch);
2686                 bp += cmatch;
2687                 buf_len += cmatch;
2688                 cmatch = 0;
2689             }
2690         }
2691
2692         // copy this char
2693         *bp++ = data[i];
2694         buf_len++;
2695     }
2696
2697         buf[buf_len] = NULLCHAR;
2698 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2699         next_out = 0;
2700         leftover_start = 0;
2701
2702         i = 0;
2703         while (i < buf_len) {
2704             /* Deal with part of the TELNET option negotiation
2705                protocol.  We refuse to do anything beyond the
2706                defaults, except that we allow the WILL ECHO option,
2707                which ICS uses to turn off password echoing when we are
2708                directly connected to it.  We reject this option
2709                if localLineEditing mode is on (always on in xboard)
2710                and we are talking to port 23, which might be a real
2711                telnet server that will try to keep WILL ECHO on permanently.
2712              */
2713             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2714                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2715                 unsigned char option;
2716                 oldi = i;
2717                 switch ((unsigned char) buf[++i]) {
2718                   case TN_WILL:
2719                     if (appData.debugMode)
2720                       fprintf(debugFP, "\n<WILL ");
2721                     switch (option = (unsigned char) buf[++i]) {
2722                       case TN_ECHO:
2723                         if (appData.debugMode)
2724                           fprintf(debugFP, "ECHO ");
2725                         /* Reply only if this is a change, according
2726                            to the protocol rules. */
2727                         if (remoteEchoOption) break;
2728                         if (appData.localLineEditing &&
2729                             atoi(appData.icsPort) == TN_PORT) {
2730                             TelnetRequest(TN_DONT, TN_ECHO);
2731                         } else {
2732                             EchoOff();
2733                             TelnetRequest(TN_DO, TN_ECHO);
2734                             remoteEchoOption = TRUE;
2735                         }
2736                         break;
2737                       default:
2738                         if (appData.debugMode)
2739                           fprintf(debugFP, "%d ", option);
2740                         /* Whatever this is, we don't want it. */
2741                         TelnetRequest(TN_DONT, option);
2742                         break;
2743                     }
2744                     break;
2745                   case TN_WONT:
2746                     if (appData.debugMode)
2747                       fprintf(debugFP, "\n<WONT ");
2748                     switch (option = (unsigned char) buf[++i]) {
2749                       case TN_ECHO:
2750                         if (appData.debugMode)
2751                           fprintf(debugFP, "ECHO ");
2752                         /* Reply only if this is a change, according
2753                            to the protocol rules. */
2754                         if (!remoteEchoOption) break;
2755                         EchoOn();
2756                         TelnetRequest(TN_DONT, TN_ECHO);
2757                         remoteEchoOption = FALSE;
2758                         break;
2759                       default:
2760                         if (appData.debugMode)
2761                           fprintf(debugFP, "%d ", (unsigned char) option);
2762                         /* Whatever this is, it must already be turned
2763                            off, because we never agree to turn on
2764                            anything non-default, so according to the
2765                            protocol rules, we don't reply. */
2766                         break;
2767                     }
2768                     break;
2769                   case TN_DO:
2770                     if (appData.debugMode)
2771                       fprintf(debugFP, "\n<DO ");
2772                     switch (option = (unsigned char) buf[++i]) {
2773                       default:
2774                         /* Whatever this is, we refuse to do it. */
2775                         if (appData.debugMode)
2776                           fprintf(debugFP, "%d ", option);
2777                         TelnetRequest(TN_WONT, option);
2778                         break;
2779                     }
2780                     break;
2781                   case TN_DONT:
2782                     if (appData.debugMode)
2783                       fprintf(debugFP, "\n<DONT ");
2784                     switch (option = (unsigned char) buf[++i]) {
2785                       default:
2786                         if (appData.debugMode)
2787                           fprintf(debugFP, "%d ", option);
2788                         /* Whatever this is, we are already not doing
2789                            it, because we never agree to do anything
2790                            non-default, so according to the protocol
2791                            rules, we don't reply. */
2792                         break;
2793                     }
2794                     break;
2795                   case TN_IAC:
2796                     if (appData.debugMode)
2797                       fprintf(debugFP, "\n<IAC ");
2798                     /* Doubled IAC; pass it through */
2799                     i--;
2800                     break;
2801                   default:
2802                     if (appData.debugMode)
2803                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2804                     /* Drop all other telnet commands on the floor */
2805                     break;
2806                 }
2807                 if (oldi > next_out)
2808                   SendToPlayer(&buf[next_out], oldi - next_out);
2809                 if (++i > next_out)
2810                   next_out = i;
2811                 continue;
2812             }
2813
2814             /* OK, this at least will *usually* work */
2815             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2816                 loggedOn = TRUE;
2817             }
2818
2819             if (loggedOn && !intfSet) {
2820                 if (ics_type == ICS_ICC) {
2821                   snprintf(str, MSG_SIZ,
2822                           "/set-quietly interface %s\n/set-quietly style 12\n",
2823                           programVersion);
2824                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2825                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2826                 } else if (ics_type == ICS_CHESSNET) {
2827                   snprintf(str, MSG_SIZ, "/style 12\n");
2828                 } else {
2829                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2830                   strcat(str, programVersion);
2831                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2832                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2833                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2834 #ifdef WIN32
2835                   strcat(str, "$iset nohighlight 1\n");
2836 #endif
2837                   strcat(str, "$iset lock 1\n$style 12\n");
2838                 }
2839                 SendToICS(str);
2840                 NotifyFrontendLogin();
2841                 intfSet = TRUE;
2842             }
2843
2844             if (started == STARTED_COMMENT) {
2845                 /* Accumulate characters in comment */
2846                 parse[parse_pos++] = buf[i];
2847                 if (buf[i] == '\n') {
2848                     parse[parse_pos] = NULLCHAR;
2849                     if(chattingPartner>=0) {
2850                         char mess[MSG_SIZ];
2851                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2852                         OutputChatMessage(chattingPartner, mess);
2853                         chattingPartner = -1;
2854                         next_out = i+1; // [HGM] suppress printing in ICS window
2855                     } else
2856                     if(!suppressKibitz) // [HGM] kibitz
2857                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2858                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2859                         int nrDigit = 0, nrAlph = 0, j;
2860                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2861                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2862                         parse[parse_pos] = NULLCHAR;
2863                         // try to be smart: if it does not look like search info, it should go to
2864                         // ICS interaction window after all, not to engine-output window.
2865                         for(j=0; j<parse_pos; j++) { // count letters and digits
2866                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2867                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2868                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2869                         }
2870                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2871                             int depth=0; float score;
2872                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2873                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2874                                 pvInfoList[forwardMostMove-1].depth = depth;
2875                                 pvInfoList[forwardMostMove-1].score = 100*score;
2876                             }
2877                             OutputKibitz(suppressKibitz, parse);
2878                         } else {
2879                             char tmp[MSG_SIZ];
2880                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2881                             SendToPlayer(tmp, strlen(tmp));
2882                         }
2883                         next_out = i+1; // [HGM] suppress printing in ICS window
2884                     }
2885                     started = STARTED_NONE;
2886                 } else {
2887                     /* Don't match patterns against characters in comment */
2888                     i++;
2889                     continue;
2890                 }
2891             }
2892             if (started == STARTED_CHATTER) {
2893                 if (buf[i] != '\n') {
2894                     /* Don't match patterns against characters in chatter */
2895                     i++;
2896                     continue;
2897                 }
2898                 started = STARTED_NONE;
2899                 if(suppressKibitz) next_out = i+1;
2900             }
2901
2902             /* Kludge to deal with rcmd protocol */
2903             if (firstTime && looking_at(buf, &i, "\001*")) {
2904                 DisplayFatalError(&buf[1], 0, 1);
2905                 continue;
2906             } else {
2907                 firstTime = FALSE;
2908             }
2909
2910             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2911                 ics_type = ICS_ICC;
2912                 ics_prefix = "/";
2913                 if (appData.debugMode)
2914                   fprintf(debugFP, "ics_type %d\n", ics_type);
2915                 continue;
2916             }
2917             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2918                 ics_type = ICS_FICS;
2919                 ics_prefix = "$";
2920                 if (appData.debugMode)
2921                   fprintf(debugFP, "ics_type %d\n", ics_type);
2922                 continue;
2923             }
2924             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2925                 ics_type = ICS_CHESSNET;
2926                 ics_prefix = "/";
2927                 if (appData.debugMode)
2928                   fprintf(debugFP, "ics_type %d\n", ics_type);
2929                 continue;
2930             }
2931
2932             if (!loggedOn &&
2933                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2934                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2935                  looking_at(buf, &i, "will be \"*\""))) {
2936               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2937               continue;
2938             }
2939
2940             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2941               char buf[MSG_SIZ];
2942               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2943               DisplayIcsInteractionTitle(buf);
2944               have_set_title = TRUE;
2945             }
2946
2947             /* skip finger notes */
2948             if (started == STARTED_NONE &&
2949                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2950                  (buf[i] == '1' && buf[i+1] == '0')) &&
2951                 buf[i+2] == ':' && buf[i+3] == ' ') {
2952               started = STARTED_CHATTER;
2953               i += 3;
2954               continue;
2955             }
2956
2957             oldi = i;
2958             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2959             if(appData.seekGraph) {
2960                 if(soughtPending && MatchSoughtLine(buf+i)) {
2961                     i = strstr(buf+i, "rated") - buf;
2962                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2963                     next_out = leftover_start = i;
2964                     started = STARTED_CHATTER;
2965                     suppressKibitz = TRUE;
2966                     continue;
2967                 }
2968                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2969                         && looking_at(buf, &i, "* ads displayed")) {
2970                     soughtPending = FALSE;
2971                     seekGraphUp = TRUE;
2972                     DrawSeekGraph();
2973                     continue;
2974                 }
2975                 if(appData.autoRefresh) {
2976                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2977                         int s = (ics_type == ICS_ICC); // ICC format differs
2978                         if(seekGraphUp)
2979                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2980                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2981                         looking_at(buf, &i, "*% "); // eat prompt
2982                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2983                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2984                         next_out = i; // suppress
2985                         continue;
2986                     }
2987                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2988                         char *p = star_match[0];
2989                         while(*p) {
2990                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2991                             while(*p && *p++ != ' '); // next
2992                         }
2993                         looking_at(buf, &i, "*% "); // eat prompt
2994                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2995                         next_out = i;
2996                         continue;
2997                     }
2998                 }
2999             }
3000
3001             /* skip formula vars */
3002             if (started == STARTED_NONE &&
3003                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3004               started = STARTED_CHATTER;
3005               i += 3;
3006               continue;
3007             }
3008
3009             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3010             if (appData.autoKibitz && started == STARTED_NONE &&
3011                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3012                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3013                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3014                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3015                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3016                         suppressKibitz = TRUE;
3017                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3018                         next_out = i;
3019                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3020                                 && (gameMode == IcsPlayingWhite)) ||
3021                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3022                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3023                             started = STARTED_CHATTER; // own kibitz we simply discard
3024                         else {
3025                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3026                             parse_pos = 0; parse[0] = NULLCHAR;
3027                             savingComment = TRUE;
3028                             suppressKibitz = gameMode != IcsObserving ? 2 :
3029                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3030                         }
3031                         continue;
3032                 } else
3033                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3034                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3035                          && atoi(star_match[0])) {
3036                     // suppress the acknowledgements of our own autoKibitz
3037                     char *p;
3038                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3040                     SendToPlayer(star_match[0], strlen(star_match[0]));
3041                     if(looking_at(buf, &i, "*% ")) // eat prompt
3042                         suppressKibitz = FALSE;
3043                     next_out = i;
3044                     continue;
3045                 }
3046             } // [HGM] kibitz: end of patch
3047
3048             // [HGM] chat: intercept tells by users for which we have an open chat window
3049             channel = -1;
3050             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3051                                            looking_at(buf, &i, "* whispers:") ||
3052                                            looking_at(buf, &i, "* kibitzes:") ||
3053                                            looking_at(buf, &i, "* shouts:") ||
3054                                            looking_at(buf, &i, "* c-shouts:") ||
3055                                            looking_at(buf, &i, "--> * ") ||
3056                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3057                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3058                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3059                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3060                 int p;
3061                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3062                 chattingPartner = -1;
3063
3064                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3065                 for(p=0; p<MAX_CHAT; p++) {
3066                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3067                     talker[0] = '['; strcat(talker, "] ");
3068                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3069                     chattingPartner = p; break;
3070                     }
3071                 } else
3072                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3073                 for(p=0; p<MAX_CHAT; p++) {
3074                     if(!strcmp("kibitzes", chatPartner[p])) {
3075                         talker[0] = '['; strcat(talker, "] ");
3076                         chattingPartner = p; break;
3077                     }
3078                 } else
3079                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3080                 for(p=0; p<MAX_CHAT; p++) {
3081                     if(!strcmp("whispers", chatPartner[p])) {
3082                         talker[0] = '['; strcat(talker, "] ");
3083                         chattingPartner = p; break;
3084                     }
3085                 } else
3086                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3087                   if(buf[i-8] == '-' && buf[i-3] == 't')
3088                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3089                     if(!strcmp("c-shouts", chatPartner[p])) {
3090                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3091                         chattingPartner = p; break;
3092                     }
3093                   }
3094                   if(chattingPartner < 0)
3095                   for(p=0; p<MAX_CHAT; p++) {
3096                     if(!strcmp("shouts", chatPartner[p])) {
3097                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3098                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3099                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3100                         chattingPartner = p; break;
3101                     }
3102                   }
3103                 }
3104                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3105                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3106                     talker[0] = 0; Colorize(ColorTell, FALSE);
3107                     chattingPartner = p; break;
3108                 }
3109                 if(chattingPartner<0) i = oldi; else {
3110                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3111                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3112                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3113                     started = STARTED_COMMENT;
3114                     parse_pos = 0; parse[0] = NULLCHAR;
3115                     savingComment = 3 + chattingPartner; // counts as TRUE
3116                     suppressKibitz = TRUE;
3117                     continue;
3118                 }
3119             } // [HGM] chat: end of patch
3120
3121           backup = i;
3122             if (appData.zippyTalk || appData.zippyPlay) {
3123                 /* [DM] Backup address for color zippy lines */
3124 #if ZIPPY
3125                if (loggedOn == TRUE)
3126                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3127                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3128 #endif
3129             } // [DM] 'else { ' deleted
3130                 if (
3131                     /* Regular tells and says */
3132                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3133                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3134                     looking_at(buf, &i, "* says: ") ||
3135                     /* Don't color "message" or "messages" output */
3136                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3137                     looking_at(buf, &i, "*. * at *:*: ") ||
3138                     looking_at(buf, &i, "--* (*:*): ") ||
3139                     /* Message notifications (same color as tells) */
3140                     looking_at(buf, &i, "* has left a message ") ||
3141                     looking_at(buf, &i, "* just sent you a message:\n") ||
3142                     /* Whispers and kibitzes */
3143                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3144                     looking_at(buf, &i, "* kibitzes: ") ||
3145                     /* Channel tells */
3146                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3147
3148                   if (tkind == 1 && strchr(star_match[0], ':')) {
3149                       /* Avoid "tells you:" spoofs in channels */
3150                      tkind = 3;
3151                   }
3152                   if (star_match[0][0] == NULLCHAR ||
3153                       strchr(star_match[0], ' ') ||
3154                       (tkind == 3 && strchr(star_match[1], ' '))) {
3155                     /* Reject bogus matches */
3156                     i = oldi;
3157                   } else {
3158                     if (appData.colorize) {
3159                       if (oldi > next_out) {
3160                         SendToPlayer(&buf[next_out], oldi - next_out);
3161                         next_out = oldi;
3162                       }
3163                       switch (tkind) {
3164                       case 1:
3165                         Colorize(ColorTell, FALSE);
3166                         curColor = ColorTell;
3167                         break;
3168                       case 2:
3169                         Colorize(ColorKibitz, FALSE);
3170                         curColor = ColorKibitz;
3171                         break;
3172                       case 3:
3173                         p = strrchr(star_match[1], '(');
3174                         if (p == NULL) {
3175                           p = star_match[1];
3176                         } else {
3177                           p++;
3178                         }
3179                         if (atoi(p) == 1) {
3180                           Colorize(ColorChannel1, FALSE);
3181                           curColor = ColorChannel1;
3182                         } else {
3183                           Colorize(ColorChannel, FALSE);
3184                           curColor = ColorChannel;
3185                         }
3186                         break;
3187                       case 5:
3188                         curColor = ColorNormal;
3189                         break;
3190                       }
3191                     }
3192                     if (started == STARTED_NONE && appData.autoComment &&
3193                         (gameMode == IcsObserving ||
3194                          gameMode == IcsPlayingWhite ||
3195                          gameMode == IcsPlayingBlack)) {
3196                       parse_pos = i - oldi;
3197                       memcpy(parse, &buf[oldi], parse_pos);
3198                       parse[parse_pos] = NULLCHAR;
3199                       started = STARTED_COMMENT;
3200                       savingComment = TRUE;
3201                     } else {
3202                       started = STARTED_CHATTER;
3203                       savingComment = FALSE;
3204                     }
3205                     loggedOn = TRUE;
3206                     continue;
3207                   }
3208                 }
3209
3210                 if (looking_at(buf, &i, "* s-shouts: ") ||
3211                     looking_at(buf, &i, "* c-shouts: ")) {
3212                     if (appData.colorize) {
3213                         if (oldi > next_out) {
3214                             SendToPlayer(&buf[next_out], oldi - next_out);
3215                             next_out = oldi;
3216                         }
3217                         Colorize(ColorSShout, FALSE);
3218                         curColor = ColorSShout;
3219                     }
3220                     loggedOn = TRUE;
3221                     started = STARTED_CHATTER;
3222                     continue;
3223                 }
3224
3225                 if (looking_at(buf, &i, "--->")) {
3226                     loggedOn = TRUE;
3227                     continue;
3228                 }
3229
3230                 if (looking_at(buf, &i, "* shouts: ") ||
3231                     looking_at(buf, &i, "--> ")) {
3232                     if (appData.colorize) {
3233                         if (oldi > next_out) {
3234                             SendToPlayer(&buf[next_out], oldi - next_out);
3235                             next_out = oldi;
3236                         }
3237                         Colorize(ColorShout, FALSE);
3238                         curColor = ColorShout;
3239                     }
3240                     loggedOn = TRUE;
3241                     started = STARTED_CHATTER;
3242                     continue;
3243                 }
3244
3245                 if (looking_at( buf, &i, "Challenge:")) {
3246                     if (appData.colorize) {
3247                         if (oldi > next_out) {
3248                             SendToPlayer(&buf[next_out], oldi - next_out);
3249                             next_out = oldi;
3250                         }
3251                         Colorize(ColorChallenge, FALSE);
3252                         curColor = ColorChallenge;
3253                     }
3254                     loggedOn = TRUE;
3255                     continue;
3256                 }
3257
3258                 if (looking_at(buf, &i, "* offers you") ||
3259                     looking_at(buf, &i, "* offers to be") ||
3260                     looking_at(buf, &i, "* would like to") ||
3261                     looking_at(buf, &i, "* requests to") ||
3262                     looking_at(buf, &i, "Your opponent offers") ||
3263                     looking_at(buf, &i, "Your opponent requests")) {
3264
3265                     if (appData.colorize) {
3266                         if (oldi > next_out) {
3267                             SendToPlayer(&buf[next_out], oldi - next_out);
3268                             next_out = oldi;
3269                         }
3270                         Colorize(ColorRequest, FALSE);
3271                         curColor = ColorRequest;
3272                     }
3273                     continue;
3274                 }
3275
3276                 if (looking_at(buf, &i, "* (*) seeking")) {
3277                     if (appData.colorize) {
3278                         if (oldi > next_out) {
3279                             SendToPlayer(&buf[next_out], oldi - next_out);
3280                             next_out = oldi;
3281                         }
3282                         Colorize(ColorSeek, FALSE);
3283                         curColor = ColorSeek;
3284                     }
3285                     continue;
3286             }
3287
3288           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3289
3290             if (looking_at(buf, &i, "\\   ")) {
3291                 if (prevColor != ColorNormal) {
3292                     if (oldi > next_out) {
3293                         SendToPlayer(&buf[next_out], oldi - next_out);
3294                         next_out = oldi;
3295                     }
3296                     Colorize(prevColor, TRUE);
3297                     curColor = prevColor;
3298                 }
3299                 if (savingComment) {
3300                     parse_pos = i - oldi;
3301                     memcpy(parse, &buf[oldi], parse_pos);
3302                     parse[parse_pos] = NULLCHAR;
3303                     started = STARTED_COMMENT;
3304                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3305                         chattingPartner = savingComment - 3; // kludge to remember the box
3306                 } else {
3307                     started = STARTED_CHATTER;
3308                 }
3309                 continue;
3310             }
3311
3312             if (looking_at(buf, &i, "Black Strength :") ||
3313                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3314                 looking_at(buf, &i, "<10>") ||
3315                 looking_at(buf, &i, "#@#")) {
3316                 /* Wrong board style */
3317                 loggedOn = TRUE;
3318                 SendToICS(ics_prefix);
3319                 SendToICS("set style 12\n");
3320                 SendToICS(ics_prefix);
3321                 SendToICS("refresh\n");
3322                 continue;
3323             }
3324
3325             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3326                 ICSInitScript();
3327                 have_sent_ICS_logon = 1;
3328                 continue;
3329             }
3330
3331             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3332                 (looking_at(buf, &i, "\n<12> ") ||
3333                  looking_at(buf, &i, "<12> "))) {
3334                 loggedOn = TRUE;
3335                 if (oldi > next_out) {
3336                     SendToPlayer(&buf[next_out], oldi - next_out);
3337                 }
3338                 next_out = i;
3339                 started = STARTED_BOARD;
3340                 parse_pos = 0;
3341                 continue;
3342             }
3343
3344             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3345                 looking_at(buf, &i, "<b1> ")) {
3346                 if (oldi > next_out) {
3347                     SendToPlayer(&buf[next_out], oldi - next_out);
3348                 }
3349                 next_out = i;
3350                 started = STARTED_HOLDINGS;
3351                 parse_pos = 0;
3352                 continue;
3353             }
3354
3355             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3356                 loggedOn = TRUE;
3357                 /* Header for a move list -- first line */
3358
3359                 switch (ics_getting_history) {
3360                   case H_FALSE:
3361                     switch (gameMode) {
3362                       case IcsIdle:
3363                       case BeginningOfGame:
3364                         /* User typed "moves" or "oldmoves" while we
3365                            were idle.  Pretend we asked for these
3366                            moves and soak them up so user can step
3367                            through them and/or save them.
3368                            */
3369                         Reset(FALSE, TRUE);
3370                         gameMode = IcsObserving;
3371                         ModeHighlight();
3372                         ics_gamenum = -1;
3373                         ics_getting_history = H_GOT_UNREQ_HEADER;
3374                         break;
3375                       case EditGame: /*?*/
3376                       case EditPosition: /*?*/
3377                         /* Should above feature work in these modes too? */
3378                         /* For now it doesn't */
3379                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3380                         break;
3381                       default:
3382                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3383                         break;
3384                     }
3385                     break;
3386                   case H_REQUESTED:
3387                     /* Is this the right one? */
3388                     if (gameInfo.white && gameInfo.black &&
3389                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3390                         strcmp(gameInfo.black, star_match[2]) == 0) {
3391                         /* All is well */
3392                         ics_getting_history = H_GOT_REQ_HEADER;
3393                     }
3394                     break;
3395                   case H_GOT_REQ_HEADER:
3396                   case H_GOT_UNREQ_HEADER:
3397                   case H_GOT_UNWANTED_HEADER:
3398                   case H_GETTING_MOVES:
3399                     /* Should not happen */
3400                     DisplayError(_("Error gathering move list: two headers"), 0);
3401                     ics_getting_history = H_FALSE;
3402                     break;
3403                 }
3404
3405                 /* Save player ratings into gameInfo if needed */
3406                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3407                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3408                     (gameInfo.whiteRating == -1 ||
3409                      gameInfo.blackRating == -1)) {
3410
3411                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3412                     gameInfo.blackRating = string_to_rating(star_match[3]);
3413                     if (appData.debugMode)
3414                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3415                               gameInfo.whiteRating, gameInfo.blackRating);
3416                 }
3417                 continue;
3418             }
3419
3420             if (looking_at(buf, &i,
3421               "* * match, initial time: * minute*, increment: * second")) {
3422                 /* Header for a move list -- second line */
3423                 /* Initial board will follow if this is a wild game */
3424                 if (gameInfo.event != NULL) free(gameInfo.event);
3425                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3426                 gameInfo.event = StrSave(str);
3427                 /* [HGM] we switched variant. Translate boards if needed. */
3428                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3429                 continue;
3430             }
3431
3432             if (looking_at(buf, &i, "Move  ")) {
3433                 /* Beginning of a move list */
3434                 switch (ics_getting_history) {
3435                   case H_FALSE:
3436                     /* Normally should not happen */
3437                     /* Maybe user hit reset while we were parsing */
3438                     break;
3439                   case H_REQUESTED:
3440                     /* Happens if we are ignoring a move list that is not
3441                      * the one we just requested.  Common if the user
3442                      * tries to observe two games without turning off
3443                      * getMoveList */
3444                     break;
3445                   case H_GETTING_MOVES:
3446                     /* Should not happen */
3447                     DisplayError(_("Error gathering move list: nested"), 0);
3448                     ics_getting_history = H_FALSE;
3449                     break;
3450                   case H_GOT_REQ_HEADER:
3451                     ics_getting_history = H_GETTING_MOVES;
3452                     started = STARTED_MOVES;
3453                     parse_pos = 0;
3454                     if (oldi > next_out) {
3455                         SendToPlayer(&buf[next_out], oldi - next_out);
3456                     }
3457                     break;
3458                   case H_GOT_UNREQ_HEADER:
3459                     ics_getting_history = H_GETTING_MOVES;
3460                     started = STARTED_MOVES_NOHIDE;
3461                     parse_pos = 0;
3462                     break;
3463                   case H_GOT_UNWANTED_HEADER:
3464                     ics_getting_history = H_FALSE;
3465                     break;
3466                 }
3467                 continue;
3468             }
3469
3470             if (looking_at(buf, &i, "% ") ||
3471                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3472                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3473                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3474                     soughtPending = FALSE;
3475                     seekGraphUp = TRUE;
3476                     DrawSeekGraph();
3477                 }
3478                 if(suppressKibitz) next_out = i;
3479                 savingComment = FALSE;
3480                 suppressKibitz = 0;
3481                 switch (started) {
3482                   case STARTED_MOVES:
3483                   case STARTED_MOVES_NOHIDE:
3484                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3485                     parse[parse_pos + i - oldi] = NULLCHAR;
3486                     ParseGameHistory(parse);
3487 #if ZIPPY
3488                     if (appData.zippyPlay && first.initDone) {
3489                         FeedMovesToProgram(&first, forwardMostMove);
3490                         if (gameMode == IcsPlayingWhite) {
3491                             if (WhiteOnMove(forwardMostMove)) {
3492                                 if (first.sendTime) {
3493                                   if (first.useColors) {
3494                                     SendToProgram("black\n", &first);
3495                                   }
3496                                   SendTimeRemaining(&first, TRUE);
3497                                 }
3498                                 if (first.useColors) {
3499                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3500                                 }
3501                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3502                                 first.maybeThinking = TRUE;
3503                             } else {
3504                                 if (first.usePlayother) {
3505                                   if (first.sendTime) {
3506                                     SendTimeRemaining(&first, TRUE);
3507                                   }
3508                                   SendToProgram("playother\n", &first);
3509                                   firstMove = FALSE;
3510                                 } else {
3511                                   firstMove = TRUE;
3512                                 }
3513                             }
3514                         } else if (gameMode == IcsPlayingBlack) {
3515                             if (!WhiteOnMove(forwardMostMove)) {
3516                                 if (first.sendTime) {
3517                                   if (first.useColors) {
3518                                     SendToProgram("white\n", &first);
3519                                   }
3520                                   SendTimeRemaining(&first, FALSE);
3521                                 }
3522                                 if (first.useColors) {
3523                                   SendToProgram("black\n", &first);
3524                                 }
3525                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3526                                 first.maybeThinking = TRUE;
3527                             } else {
3528                                 if (first.usePlayother) {
3529                                   if (first.sendTime) {
3530                                     SendTimeRemaining(&first, FALSE);
3531                                   }
3532                                   SendToProgram("playother\n", &first);
3533                                   firstMove = FALSE;
3534                                 } else {
3535                                   firstMove = TRUE;
3536                                 }
3537                             }
3538                         }
3539                     }
3540 #endif
3541                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3542                         /* Moves came from oldmoves or moves command
3543                            while we weren't doing anything else.
3544                            */
3545                         currentMove = forwardMostMove;
3546                         ClearHighlights();/*!!could figure this out*/
3547                         flipView = appData.flipView;
3548                         DrawPosition(TRUE, boards[currentMove]);
3549                         DisplayBothClocks();
3550                         snprintf(str, MSG_SIZ, "%s vs. %s",
3551                                 gameInfo.white, gameInfo.black);
3552                         DisplayTitle(str);
3553                         gameMode = IcsIdle;
3554                     } else {
3555                         /* Moves were history of an active game */
3556                         if (gameInfo.resultDetails != NULL) {
3557                             free(gameInfo.resultDetails);
3558                             gameInfo.resultDetails = NULL;
3559                         }
3560                     }
3561                     HistorySet(parseList, backwardMostMove,
3562                                forwardMostMove, currentMove-1);
3563                     DisplayMove(currentMove - 1);
3564                     if (started == STARTED_MOVES) next_out = i;
3565                     started = STARTED_NONE;
3566                     ics_getting_history = H_FALSE;
3567                     break;
3568
3569                   case STARTED_OBSERVE:
3570                     started = STARTED_NONE;
3571                     SendToICS(ics_prefix);
3572                     SendToICS("refresh\n");
3573                     break;
3574
3575                   default:
3576                     break;
3577                 }
3578                 if(bookHit) { // [HGM] book: simulate book reply
3579                     static char bookMove[MSG_SIZ]; // a bit generous?
3580
3581                     programStats.nodes = programStats.depth = programStats.time =
3582                     programStats.score = programStats.got_only_move = 0;
3583                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3584
3585                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3586                     strcat(bookMove, bookHit);
3587                     HandleMachineMove(bookMove, &first);
3588                 }
3589                 continue;
3590             }
3591
3592             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3593                  started == STARTED_HOLDINGS ||
3594                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3595                 /* Accumulate characters in move list or board */
3596                 parse[parse_pos++] = buf[i];
3597             }
3598
3599             /* Start of game messages.  Mostly we detect start of game
3600                when the first board image arrives.  On some versions
3601                of the ICS, though, we need to do a "refresh" after starting
3602                to observe in order to get the current board right away. */
3603             if (looking_at(buf, &i, "Adding game * to observation list")) {
3604                 started = STARTED_OBSERVE;
3605                 continue;
3606             }
3607
3608             /* Handle auto-observe */
3609             if (appData.autoObserve &&
3610                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3611                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3612                 char *player;
3613                 /* Choose the player that was highlighted, if any. */
3614                 if (star_match[0][0] == '\033' ||
3615                     star_match[1][0] != '\033') {
3616                     player = star_match[0];
3617                 } else {
3618                     player = star_match[2];
3619                 }
3620                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3621                         ics_prefix, StripHighlightAndTitle(player));
3622                 SendToICS(str);
3623
3624                 /* Save ratings from notify string */
3625                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3626                 player1Rating = string_to_rating(star_match[1]);
3627                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3628                 player2Rating = string_to_rating(star_match[3]);
3629
3630                 if (appData.debugMode)
3631                   fprintf(debugFP,
3632                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3633                           player1Name, player1Rating,
3634                           player2Name, player2Rating);
3635
3636                 continue;
3637             }
3638
3639             /* Deal with automatic examine mode after a game,
3640                and with IcsObserving -> IcsExamining transition */
3641             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3642                 looking_at(buf, &i, "has made you an examiner of game *")) {
3643
3644                 int gamenum = atoi(star_match[0]);
3645                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3646                     gamenum == ics_gamenum) {
3647                     /* We were already playing or observing this game;
3648                        no need to refetch history */
3649                     gameMode = IcsExamining;
3650                     if (pausing) {
3651                         pauseExamForwardMostMove = forwardMostMove;
3652                     } else if (currentMove < forwardMostMove) {
3653                         ForwardInner(forwardMostMove);
3654                     }
3655                 } else {
3656                     /* I don't think this case really can happen */
3657                     SendToICS(ics_prefix);
3658                     SendToICS("refresh\n");
3659                 }
3660                 continue;
3661             }
3662
3663             /* Error messages */
3664 //          if (ics_user_moved) {
3665             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3666                 if (looking_at(buf, &i, "Illegal move") ||
3667                     looking_at(buf, &i, "Not a legal move") ||
3668                     looking_at(buf, &i, "Your king is in check") ||
3669                     looking_at(buf, &i, "It isn't your turn") ||
3670                     looking_at(buf, &i, "It is not your move")) {
3671                     /* Illegal move */
3672                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3673                         currentMove = forwardMostMove-1;
3674                         DisplayMove(currentMove - 1); /* before DMError */
3675                         DrawPosition(FALSE, boards[currentMove]);
3676                         SwitchClocks(forwardMostMove-1); // [HGM] race
3677                         DisplayBothClocks();
3678                     }
3679                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3680                     ics_user_moved = 0;
3681                     continue;
3682                 }
3683             }
3684
3685             if (looking_at(buf, &i, "still have time") ||
3686                 looking_at(buf, &i, "not out of time") ||
3687                 looking_at(buf, &i, "either player is out of time") ||
3688                 looking_at(buf, &i, "has timeseal; checking")) {
3689                 /* We must have called his flag a little too soon */
3690                 whiteFlag = blackFlag = FALSE;
3691                 continue;
3692             }
3693
3694             if (looking_at(buf, &i, "added * seconds to") ||
3695                 looking_at(buf, &i, "seconds were added to")) {
3696                 /* Update the clocks */
3697                 SendToICS(ics_prefix);
3698                 SendToICS("refresh\n");
3699                 continue;
3700             }
3701
3702             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3703                 ics_clock_paused = TRUE;
3704                 StopClocks();
3705                 continue;
3706             }
3707
3708             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3709                 ics_clock_paused = FALSE;
3710                 StartClocks();
3711                 continue;
3712             }
3713
3714             /* Grab player ratings from the Creating: message.
3715                Note we have to check for the special case when
3716                the ICS inserts things like [white] or [black]. */
3717             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3718                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3719                 /* star_matches:
3720                    0    player 1 name (not necessarily white)
3721                    1    player 1 rating
3722                    2    empty, white, or black (IGNORED)
3723                    3    player 2 name (not necessarily black)
3724                    4    player 2 rating
3725
3726                    The names/ratings are sorted out when the game
3727                    actually starts (below).
3728                 */
3729                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3730                 player1Rating = string_to_rating(star_match[1]);
3731                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3732                 player2Rating = string_to_rating(star_match[4]);
3733
3734                 if (appData.debugMode)
3735                   fprintf(debugFP,
3736                           "Ratings from 'Creating:' %s %d, %s %d\n",
3737                           player1Name, player1Rating,
3738                           player2Name, player2Rating);
3739
3740                 continue;
3741             }
3742
3743             /* Improved generic start/end-of-game messages */
3744             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3745                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3746                 /* If tkind == 0: */
3747                 /* star_match[0] is the game number */
3748                 /*           [1] is the white player's name */
3749                 /*           [2] is the black player's name */
3750                 /* For end-of-game: */
3751                 /*           [3] is the reason for the game end */
3752                 /*           [4] is a PGN end game-token, preceded by " " */
3753                 /* For start-of-game: */
3754                 /*           [3] begins with "Creating" or "Continuing" */
3755                 /*           [4] is " *" or empty (don't care). */
3756                 int gamenum = atoi(star_match[0]);
3757                 char *whitename, *blackname, *why, *endtoken;
3758                 ChessMove endtype = EndOfFile;
3759
3760                 if (tkind == 0) {
3761                   whitename = star_match[1];
3762                   blackname = star_match[2];
3763                   why = star_match[3];
3764                   endtoken = star_match[4];
3765                 } else {
3766                   whitename = star_match[1];
3767                   blackname = star_match[3];
3768                   why = star_match[5];
3769                   endtoken = star_match[6];
3770                 }
3771
3772                 /* Game start messages */
3773                 if (strncmp(why, "Creating ", 9) == 0 ||
3774                     strncmp(why, "Continuing ", 11) == 0) {
3775                     gs_gamenum = gamenum;
3776                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3777                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3778 #if ZIPPY
3779                     if (appData.zippyPlay) {
3780                         ZippyGameStart(whitename, blackname);
3781                     }
3782 #endif /*ZIPPY*/
3783                     partnerBoardValid = FALSE; // [HGM] bughouse
3784                     continue;
3785                 }
3786
3787                 /* Game end messages */
3788                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3789                     ics_gamenum != gamenum) {
3790                     continue;
3791                 }
3792                 while (endtoken[0] == ' ') endtoken++;
3793                 switch (endtoken[0]) {
3794                   case '*':
3795                   default:
3796                     endtype = GameUnfinished;
3797                     break;
3798                   case '0':
3799                     endtype = BlackWins;
3800                     break;
3801                   case '1':
3802                     if (endtoken[1] == '/')
3803                       endtype = GameIsDrawn;
3804                     else
3805                       endtype = WhiteWins;
3806                     break;
3807                 }
3808                 GameEnds(endtype, why, GE_ICS);
3809 #if ZIPPY
3810                 if (appData.zippyPlay && first.initDone) {
3811                     ZippyGameEnd(endtype, why);
3812                     if (first.pr == NULL) {
3813                       /* Start the next process early so that we'll
3814                          be ready for the next challenge */
3815                       StartChessProgram(&first);
3816                     }
3817                     /* Send "new" early, in case this command takes
3818                        a long time to finish, so that we'll be ready
3819                        for the next challenge. */
3820                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3821                     Reset(TRUE, TRUE);
3822                 }
3823 #endif /*ZIPPY*/
3824                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3825                 continue;
3826             }
3827
3828             if (looking_at(buf, &i, "Removing game * from observation") ||
3829                 looking_at(buf, &i, "no longer observing game *") ||
3830                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3831                 if (gameMode == IcsObserving &&
3832                     atoi(star_match[0]) == ics_gamenum)
3833                   {
3834                       /* icsEngineAnalyze */
3835                       if (appData.icsEngineAnalyze) {
3836                             ExitAnalyzeMode();
3837                             ModeHighlight();
3838                       }
3839                       StopClocks();
3840                       gameMode = IcsIdle;
3841                       ics_gamenum = -1;
3842                       ics_user_moved = FALSE;
3843                   }
3844                 continue;
3845             }
3846
3847             if (looking_at(buf, &i, "no longer examining game *")) {
3848                 if (gameMode == IcsExamining &&
3849                     atoi(star_match[0]) == ics_gamenum)
3850                   {
3851                       gameMode = IcsIdle;
3852                       ics_gamenum = -1;
3853                       ics_user_moved = FALSE;
3854                   }
3855                 continue;
3856             }
3857
3858             /* Advance leftover_start past any newlines we find,
3859                so only partial lines can get reparsed */
3860             if (looking_at(buf, &i, "\n")) {
3861                 prevColor = curColor;
3862                 if (curColor != ColorNormal) {
3863                     if (oldi > next_out) {
3864                         SendToPlayer(&buf[next_out], oldi - next_out);
3865                         next_out = oldi;
3866                     }
3867                     Colorize(ColorNormal, FALSE);
3868                     curColor = ColorNormal;
3869                 }
3870                 if (started == STARTED_BOARD) {
3871                     started = STARTED_NONE;
3872                     parse[parse_pos] = NULLCHAR;
3873                     ParseBoard12(parse);
3874                     ics_user_moved = 0;
3875
3876                     /* Send premove here */
3877                     if (appData.premove) {
3878                       char str[MSG_SIZ];
3879                       if (currentMove == 0 &&
3880                           gameMode == IcsPlayingWhite &&
3881                           appData.premoveWhite) {
3882                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3883                         if (appData.debugMode)
3884                           fprintf(debugFP, "Sending premove:\n");
3885                         SendToICS(str);
3886                       } else if (currentMove == 1 &&
3887                                  gameMode == IcsPlayingBlack &&
3888                                  appData.premoveBlack) {
3889                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3890                         if (appData.debugMode)
3891                           fprintf(debugFP, "Sending premove:\n");
3892                         SendToICS(str);
3893                       } else if (gotPremove) {
3894                         gotPremove = 0;
3895                         ClearPremoveHighlights();
3896                         if (appData.debugMode)
3897                           fprintf(debugFP, "Sending premove:\n");
3898                           UserMoveEvent(premoveFromX, premoveFromY,
3899                                         premoveToX, premoveToY,
3900                                         premovePromoChar);
3901                       }
3902                     }
3903
3904                     /* Usually suppress following prompt */
3905                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3906                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3907                         if (looking_at(buf, &i, "*% ")) {
3908                             savingComment = FALSE;
3909                             suppressKibitz = 0;
3910                         }
3911                     }
3912                     next_out = i;
3913                 } else if (started == STARTED_HOLDINGS) {
3914                     int gamenum;
3915                     char new_piece[MSG_SIZ];
3916                     started = STARTED_NONE;
3917                     parse[parse_pos] = NULLCHAR;
3918                     if (appData.debugMode)
3919                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3920                                                         parse, currentMove);
3921                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3922                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3923                         if (gameInfo.variant == VariantNormal) {
3924                           /* [HGM] We seem to switch variant during a game!
3925                            * Presumably no holdings were displayed, so we have
3926                            * to move the position two files to the right to
3927                            * create room for them!
3928                            */
3929                           VariantClass newVariant;
3930                           switch(gameInfo.boardWidth) { // base guess on board width
3931                                 case 9:  newVariant = VariantShogi; break;
3932                                 case 10: newVariant = VariantGreat; break;
3933                                 default: newVariant = VariantCrazyhouse; break;
3934                           }
3935                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3936                           /* Get a move list just to see the header, which
3937                              will tell us whether this is really bug or zh */
3938                           if (ics_getting_history == H_FALSE) {
3939                             ics_getting_history = H_REQUESTED;
3940                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3941                             SendToICS(str);
3942                           }
3943                         }
3944                         new_piece[0] = NULLCHAR;
3945                         sscanf(parse, "game %d white [%s black [%s <- %s",
3946                                &gamenum, white_holding, black_holding,
3947                                new_piece);
3948                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3949                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3950                         /* [HGM] copy holdings to board holdings area */
3951                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3952                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3953                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3954 #if ZIPPY
3955                         if (appData.zippyPlay && first.initDone) {
3956                             ZippyHoldings(white_holding, black_holding,
3957                                           new_piece);
3958                         }
3959 #endif /*ZIPPY*/
3960                         if (tinyLayout || smallLayout) {
3961                             char wh[16], bh[16];
3962                             PackHolding(wh, white_holding);
3963                             PackHolding(bh, black_holding);
3964                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3965                                     gameInfo.white, gameInfo.black);
3966                         } else {
3967                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3968                                     gameInfo.white, white_holding,
3969                                     gameInfo.black, black_holding);
3970                         }
3971                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3972                         DrawPosition(FALSE, boards[currentMove]);
3973                         DisplayTitle(str);
3974                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3975                         sscanf(parse, "game %d white [%s black [%s <- %s",
3976                                &gamenum, white_holding, black_holding,
3977                                new_piece);
3978                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3979                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3980                         /* [HGM] copy holdings to partner-board holdings area */
3981                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3982                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3983                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3984                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3985                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3986                       }
3987                     }
3988                     /* Suppress following prompt */
3989                     if (looking_at(buf, &i, "*% ")) {
3990                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3991                         savingComment = FALSE;
3992                         suppressKibitz = 0;
3993                     }
3994                     next_out = i;
3995                 }
3996                 continue;
3997             }
3998
3999             i++;                /* skip unparsed character and loop back */
4000         }
4001
4002         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4003 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4004 //          SendToPlayer(&buf[next_out], i - next_out);
4005             started != STARTED_HOLDINGS && leftover_start > next_out) {
4006             SendToPlayer(&buf[next_out], leftover_start - next_out);
4007             next_out = i;
4008         }
4009
4010         leftover_len = buf_len - leftover_start;
4011         /* if buffer ends with something we couldn't parse,
4012            reparse it after appending the next read */
4013
4014     } else if (count == 0) {
4015         RemoveInputSource(isr);
4016         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4017     } else {
4018         DisplayFatalError(_("Error reading from ICS"), error, 1);
4019     }
4020 }
4021
4022
4023 /* Board style 12 looks like this:
4024
4025    <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
4026
4027  * The "<12> " is stripped before it gets to this routine.  The two
4028  * trailing 0's (flip state and clock ticking) are later addition, and
4029  * some chess servers may not have them, or may have only the first.
4030  * Additional trailing fields may be added in the future.
4031  */
4032
4033 #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"
4034
4035 #define RELATION_OBSERVING_PLAYED    0
4036 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4037 #define RELATION_PLAYING_MYMOVE      1
4038 #define RELATION_PLAYING_NOTMYMOVE  -1
4039 #define RELATION_EXAMINING           2
4040 #define RELATION_ISOLATED_BOARD     -3
4041 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4042
4043 void
4044 ParseBoard12(string)
4045      char *string;
4046 {
4047     GameMode newGameMode;
4048     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4049     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4050     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4051     char to_play, board_chars[200];
4052     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4053     char black[32], white[32];
4054     Board board;
4055     int prevMove = currentMove;
4056     int ticking = 2;
4057     ChessMove moveType;
4058     int fromX, fromY, toX, toY;
4059     char promoChar;
4060     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4061     char *bookHit = NULL; // [HGM] book
4062     Boolean weird = FALSE, reqFlag = FALSE;
4063
4064     fromX = fromY = toX = toY = -1;
4065
4066     newGame = FALSE;
4067
4068     if (appData.debugMode)
4069       fprintf(debugFP, _("Parsing board: %s\n"), string);
4070
4071     move_str[0] = NULLCHAR;
4072     elapsed_time[0] = NULLCHAR;
4073     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4074         int  i = 0, j;
4075         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4076             if(string[i] == ' ') { ranks++; files = 0; }
4077             else files++;
4078             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4079             i++;
4080         }
4081         for(j = 0; j <i; j++) board_chars[j] = string[j];
4082         board_chars[i] = '\0';
4083         string += i + 1;
4084     }
4085     n = sscanf(string, PATTERN, &to_play, &double_push,
4086                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4087                &gamenum, white, black, &relation, &basetime, &increment,
4088                &white_stren, &black_stren, &white_time, &black_time,
4089                &moveNum, str, elapsed_time, move_str, &ics_flip,
4090                &ticking);
4091
4092     if (n < 21) {
4093         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4094         DisplayError(str, 0);
4095         return;
4096     }
4097
4098     /* Convert the move number to internal form */
4099     moveNum = (moveNum - 1) * 2;
4100     if (to_play == 'B') moveNum++;
4101     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4102       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4103                         0, 1);
4104       return;
4105     }
4106
4107     switch (relation) {
4108       case RELATION_OBSERVING_PLAYED:
4109       case RELATION_OBSERVING_STATIC:
4110         if (gamenum == -1) {
4111             /* Old ICC buglet */
4112             relation = RELATION_OBSERVING_STATIC;
4113         }
4114         newGameMode = IcsObserving;
4115         break;
4116       case RELATION_PLAYING_MYMOVE:
4117       case RELATION_PLAYING_NOTMYMOVE:
4118         newGameMode =
4119           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4120             IcsPlayingWhite : IcsPlayingBlack;
4121         break;
4122       case RELATION_EXAMINING:
4123         newGameMode = IcsExamining;
4124         break;
4125       case RELATION_ISOLATED_BOARD:
4126       default:
4127         /* Just display this board.  If user was doing something else,
4128            we will forget about it until the next board comes. */
4129         newGameMode = IcsIdle;
4130         break;
4131       case RELATION_STARTING_POSITION:
4132         newGameMode = gameMode;
4133         break;
4134     }
4135
4136     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4137          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4138       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4139       char *toSqr;
4140       for (k = 0; k < ranks; k++) {
4141         for (j = 0; j < files; j++)
4142           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143         if(gameInfo.holdingsWidth > 1) {
4144              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4146         }
4147       }
4148       CopyBoard(partnerBoard, board);
4149       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4150         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4151         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4152       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4153       if(toSqr = strchr(str, '-')) {
4154         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4155         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4156       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4157       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4158       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4159       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4160       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4161       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4162                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4163       DisplayMessage(partnerStatus, "");
4164         partnerBoardValid = TRUE;
4165       return;
4166     }
4167
4168     /* Modify behavior for initial board display on move listing
4169        of wild games.
4170        */
4171     switch (ics_getting_history) {
4172       case H_FALSE:
4173       case H_REQUESTED:
4174         break;
4175       case H_GOT_REQ_HEADER:
4176       case H_GOT_UNREQ_HEADER:
4177         /* This is the initial position of the current game */
4178         gamenum = ics_gamenum;
4179         moveNum = 0;            /* old ICS bug workaround */
4180         if (to_play == 'B') {
4181           startedFromSetupPosition = TRUE;
4182           blackPlaysFirst = TRUE;
4183           moveNum = 1;
4184           if (forwardMostMove == 0) forwardMostMove = 1;
4185           if (backwardMostMove == 0) backwardMostMove = 1;
4186           if (currentMove == 0) currentMove = 1;
4187         }
4188         newGameMode = gameMode;
4189         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4190         break;
4191       case H_GOT_UNWANTED_HEADER:
4192         /* This is an initial board that we don't want */
4193         return;
4194       case H_GETTING_MOVES:
4195         /* Should not happen */
4196         DisplayError(_("Error gathering move list: extra board"), 0);
4197         ics_getting_history = H_FALSE;
4198         return;
4199     }
4200
4201    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4202                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4203      /* [HGM] We seem to have switched variant unexpectedly
4204       * Try to guess new variant from board size
4205       */
4206           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4207           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4208           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4209           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4210           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4211           if(!weird) newVariant = VariantNormal;
4212           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4213           /* Get a move list just to see the header, which
4214              will tell us whether this is really bug or zh */
4215           if (ics_getting_history == H_FALSE) {
4216             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4217             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4218             SendToICS(str);
4219           }
4220     }
4221
4222     /* Take action if this is the first board of a new game, or of a
4223        different game than is currently being displayed.  */
4224     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4225         relation == RELATION_ISOLATED_BOARD) {
4226
4227         /* Forget the old game and get the history (if any) of the new one */
4228         if (gameMode != BeginningOfGame) {
4229           Reset(TRUE, TRUE);
4230         }
4231         newGame = TRUE;
4232         if (appData.autoRaiseBoard) BoardToTop();
4233         prevMove = -3;
4234         if (gamenum == -1) {
4235             newGameMode = IcsIdle;
4236         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4237                    appData.getMoveList && !reqFlag) {
4238             /* Need to get game history */
4239             ics_getting_history = H_REQUESTED;
4240             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4241             SendToICS(str);
4242         }
4243
4244         /* Initially flip the board to have black on the bottom if playing
4245            black or if the ICS flip flag is set, but let the user change
4246            it with the Flip View button. */
4247         flipView = appData.autoFlipView ?
4248           (newGameMode == IcsPlayingBlack) || ics_flip :
4249           appData.flipView;
4250
4251         /* Done with values from previous mode; copy in new ones */
4252         gameMode = newGameMode;
4253         ModeHighlight();
4254         ics_gamenum = gamenum;
4255         if (gamenum == gs_gamenum) {
4256             int klen = strlen(gs_kind);
4257             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4258             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4259             gameInfo.event = StrSave(str);
4260         } else {
4261             gameInfo.event = StrSave("ICS game");
4262         }
4263         gameInfo.site = StrSave(appData.icsHost);
4264         gameInfo.date = PGNDate();
4265         gameInfo.round = StrSave("-");
4266         gameInfo.white = StrSave(white);
4267         gameInfo.black = StrSave(black);
4268         timeControl = basetime * 60 * 1000;
4269         timeControl_2 = 0;
4270         timeIncrement = increment * 1000;
4271         movesPerSession = 0;
4272         gameInfo.timeControl = TimeControlTagValue();
4273         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4274   if (appData.debugMode) {
4275     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4276     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4277     setbuf(debugFP, NULL);
4278   }
4279
4280         gameInfo.outOfBook = NULL;
4281
4282         /* Do we have the ratings? */
4283         if (strcmp(player1Name, white) == 0 &&
4284             strcmp(player2Name, black) == 0) {
4285             if (appData.debugMode)
4286               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4287                       player1Rating, player2Rating);
4288             gameInfo.whiteRating = player1Rating;
4289             gameInfo.blackRating = player2Rating;
4290         } else if (strcmp(player2Name, white) == 0 &&
4291                    strcmp(player1Name, black) == 0) {
4292             if (appData.debugMode)
4293               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4294                       player2Rating, player1Rating);
4295             gameInfo.whiteRating = player2Rating;
4296             gameInfo.blackRating = player1Rating;
4297         }
4298         player1Name[0] = player2Name[0] = NULLCHAR;
4299
4300         /* Silence shouts if requested */
4301         if (appData.quietPlay &&
4302             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4303             SendToICS(ics_prefix);
4304             SendToICS("set shout 0\n");
4305         }
4306     }
4307
4308     /* Deal with midgame name changes */
4309     if (!newGame) {
4310         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4311             if (gameInfo.white) free(gameInfo.white);
4312             gameInfo.white = StrSave(white);
4313         }
4314         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4315             if (gameInfo.black) free(gameInfo.black);
4316             gameInfo.black = StrSave(black);
4317         }
4318     }
4319
4320     /* Throw away game result if anything actually changes in examine mode */
4321     if (gameMode == IcsExamining && !newGame) {
4322         gameInfo.result = GameUnfinished;
4323         if (gameInfo.resultDetails != NULL) {
4324             free(gameInfo.resultDetails);
4325             gameInfo.resultDetails = NULL;
4326         }
4327     }
4328
4329     /* In pausing && IcsExamining mode, we ignore boards coming
4330        in if they are in a different variation than we are. */
4331     if (pauseExamInvalid) return;
4332     if (pausing && gameMode == IcsExamining) {
4333         if (moveNum <= pauseExamForwardMostMove) {
4334             pauseExamInvalid = TRUE;
4335             forwardMostMove = pauseExamForwardMostMove;
4336             return;
4337         }
4338     }
4339
4340   if (appData.debugMode) {
4341     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4342   }
4343     /* Parse the board */
4344     for (k = 0; k < ranks; k++) {
4345       for (j = 0; j < files; j++)
4346         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4347       if(gameInfo.holdingsWidth > 1) {
4348            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4349            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4350       }
4351     }
4352     CopyBoard(boards[moveNum], board);
4353     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4354     if (moveNum == 0) {
4355         startedFromSetupPosition =
4356           !CompareBoards(board, initialPosition);
4357         if(startedFromSetupPosition)
4358             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4359     }
4360
4361     /* [HGM] Set castling rights. Take the outermost Rooks,
4362        to make it also work for FRC opening positions. Note that board12
4363        is really defective for later FRC positions, as it has no way to
4364        indicate which Rook can castle if they are on the same side of King.
4365        For the initial position we grant rights to the outermost Rooks,
4366        and remember thos rights, and we then copy them on positions
4367        later in an FRC game. This means WB might not recognize castlings with
4368        Rooks that have moved back to their original position as illegal,
4369        but in ICS mode that is not its job anyway.
4370     */
4371     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4372     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4373
4374         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4375             if(board[0][i] == WhiteRook) j = i;
4376         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4377         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4378             if(board[0][i] == WhiteRook) j = i;
4379         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4380         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4381             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4382         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4383         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4384             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4385         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4386
4387         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4388         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4389             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4390         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4391             if(board[BOARD_HEIGHT-1][k] == bKing)
4392                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4393         if(gameInfo.variant == VariantTwoKings) {
4394             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4395             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4396             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4397         }
4398     } else { int r;
4399         r = boards[moveNum][CASTLING][0] = initialRights[0];
4400         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4401         r = boards[moveNum][CASTLING][1] = initialRights[1];
4402         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4403         r = boards[moveNum][CASTLING][3] = initialRights[3];
4404         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4405         r = boards[moveNum][CASTLING][4] = initialRights[4];
4406         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4407         /* wildcastle kludge: always assume King has rights */
4408         r = boards[moveNum][CASTLING][2] = initialRights[2];
4409         r = boards[moveNum][CASTLING][5] = initialRights[5];
4410     }
4411     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4412     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4413
4414
4415     if (ics_getting_history == H_GOT_REQ_HEADER ||
4416         ics_getting_history == H_GOT_UNREQ_HEADER) {
4417         /* This was an initial position from a move list, not
4418            the current position */
4419         return;
4420     }
4421
4422     /* Update currentMove and known move number limits */
4423     newMove = newGame || moveNum > forwardMostMove;
4424
4425     if (newGame) {
4426         forwardMostMove = backwardMostMove = currentMove = moveNum;
4427         if (gameMode == IcsExamining && moveNum == 0) {
4428           /* Workaround for ICS limitation: we are not told the wild
4429              type when starting to examine a game.  But if we ask for
4430              the move list, the move list header will tell us */
4431             ics_getting_history = H_REQUESTED;
4432             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4433             SendToICS(str);
4434         }
4435     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4436                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4437 #if ZIPPY
4438         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4439         /* [HGM] applied this also to an engine that is silently watching        */
4440         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4441             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4442             gameInfo.variant == currentlyInitializedVariant) {
4443           takeback = forwardMostMove - moveNum;
4444           for (i = 0; i < takeback; i++) {
4445             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4446             SendToProgram("undo\n", &first);
4447           }
4448         }
4449 #endif
4450
4451         forwardMostMove = moveNum;
4452         if (!pausing || currentMove > forwardMostMove)
4453           currentMove = forwardMostMove;
4454     } else {
4455         /* New part of history that is not contiguous with old part */
4456         if (pausing && gameMode == IcsExamining) {
4457             pauseExamInvalid = TRUE;
4458             forwardMostMove = pauseExamForwardMostMove;
4459             return;
4460         }
4461         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4462 #if ZIPPY
4463             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4464                 // [HGM] when we will receive the move list we now request, it will be
4465                 // fed to the engine from the first move on. So if the engine is not
4466                 // in the initial position now, bring it there.
4467                 InitChessProgram(&first, 0);
4468             }
4469 #endif
4470             ics_getting_history = H_REQUESTED;
4471             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4472             SendToICS(str);
4473         }
4474         forwardMostMove = backwardMostMove = currentMove = moveNum;
4475     }
4476
4477     /* Update the clocks */
4478     if (strchr(elapsed_time, '.')) {
4479       /* Time is in ms */
4480       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4481       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4482     } else {
4483       /* Time is in seconds */
4484       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4485       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4486     }
4487
4488
4489 #if ZIPPY
4490     if (appData.zippyPlay && newGame &&
4491         gameMode != IcsObserving && gameMode != IcsIdle &&
4492         gameMode != IcsExamining)
4493       ZippyFirstBoard(moveNum, basetime, increment);
4494 #endif
4495
4496     /* Put the move on the move list, first converting
4497        to canonical algebraic form. */
4498     if (moveNum > 0) {
4499   if (appData.debugMode) {
4500     if (appData.debugMode) { int f = forwardMostMove;
4501         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4502                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4503                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4504     }
4505     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4506     fprintf(debugFP, "moveNum = %d\n", moveNum);
4507     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4508     setbuf(debugFP, NULL);
4509   }
4510         if (moveNum <= backwardMostMove) {
4511             /* We don't know what the board looked like before
4512                this move.  Punt. */
4513           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4514             strcat(parseList[moveNum - 1], " ");
4515             strcat(parseList[moveNum - 1], elapsed_time);
4516             moveList[moveNum - 1][0] = NULLCHAR;
4517         } else if (strcmp(move_str, "none") == 0) {
4518             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4519             /* Again, we don't know what the board looked like;
4520                this is really the start of the game. */
4521             parseList[moveNum - 1][0] = NULLCHAR;
4522             moveList[moveNum - 1][0] = NULLCHAR;
4523             backwardMostMove = moveNum;
4524             startedFromSetupPosition = TRUE;
4525             fromX = fromY = toX = toY = -1;
4526         } else {
4527           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4528           //                 So we parse the long-algebraic move string in stead of the SAN move
4529           int valid; char buf[MSG_SIZ], *prom;
4530
4531           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4532                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4533           // str looks something like "Q/a1-a2"; kill the slash
4534           if(str[1] == '/')
4535             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4536           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4537           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4538                 strcat(buf, prom); // long move lacks promo specification!
4539           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4540                 if(appData.debugMode)
4541                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4542                 safeStrCpy(move_str, buf, MSG_SIZ);
4543           }
4544           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4545                                 &fromX, &fromY, &toX, &toY, &promoChar)
4546                || ParseOneMove(buf, moveNum - 1, &moveType,
4547                                 &fromX, &fromY, &toX, &toY, &promoChar);
4548           // end of long SAN patch
4549           if (valid) {
4550             (void) CoordsToAlgebraic(boards[moveNum - 1],
4551                                      PosFlags(moveNum - 1),
4552                                      fromY, fromX, toY, toX, promoChar,
4553                                      parseList[moveNum-1]);
4554             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4555               case MT_NONE:
4556               case MT_STALEMATE:
4557               default:
4558                 break;
4559               case MT_CHECK:
4560                 if(gameInfo.variant != VariantShogi)
4561                     strcat(parseList[moveNum - 1], "+");
4562                 break;
4563               case MT_CHECKMATE:
4564               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4565                 strcat(parseList[moveNum - 1], "#");
4566                 break;
4567             }
4568             strcat(parseList[moveNum - 1], " ");
4569             strcat(parseList[moveNum - 1], elapsed_time);
4570             /* currentMoveString is set as a side-effect of ParseOneMove */
4571             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4572             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4573             strcat(moveList[moveNum - 1], "\n");
4574
4575             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4576                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4577               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4578                 ChessSquare old, new = boards[moveNum][k][j];
4579                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4580                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4581                   if(old == new) continue;
4582                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4583                   else if(new == WhiteWazir || new == BlackWazir) {
4584                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4585                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4586                       else boards[moveNum][k][j] = old; // preserve type of Gold
4587                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4588                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4589               }
4590           } else {
4591             /* Move from ICS was illegal!?  Punt. */
4592             if (appData.debugMode) {
4593               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4594               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4595             }
4596             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4597             strcat(parseList[moveNum - 1], " ");
4598             strcat(parseList[moveNum - 1], elapsed_time);
4599             moveList[moveNum - 1][0] = NULLCHAR;
4600             fromX = fromY = toX = toY = -1;
4601           }
4602         }
4603   if (appData.debugMode) {
4604     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4605     setbuf(debugFP, NULL);
4606   }
4607
4608 #if ZIPPY
4609         /* Send move to chess program (BEFORE animating it). */
4610         if (appData.zippyPlay && !newGame && newMove &&
4611            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4612
4613             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4614                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4615                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4616                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4617                             move_str);
4618                     DisplayError(str, 0);
4619                 } else {
4620                     if (first.sendTime) {
4621                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4622                     }
4623                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4624                     if (firstMove && !bookHit) {
4625                         firstMove = FALSE;
4626                         if (first.useColors) {
4627                           SendToProgram(gameMode == IcsPlayingWhite ?
4628                                         "white\ngo\n" :
4629                                         "black\ngo\n", &first);
4630                         } else {
4631                           SendToProgram("go\n", &first);
4632                         }
4633                         first.maybeThinking = TRUE;
4634                     }
4635                 }
4636             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4637               if (moveList[moveNum - 1][0] == NULLCHAR) {
4638                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4639                 DisplayError(str, 0);
4640               } else {
4641                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4642                 SendMoveToProgram(moveNum - 1, &first);
4643               }
4644             }
4645         }
4646 #endif
4647     }
4648
4649     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4650         /* If move comes from a remote source, animate it.  If it
4651            isn't remote, it will have already been animated. */
4652         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4653             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4654         }
4655         if (!pausing && appData.highlightLastMove) {
4656             SetHighlights(fromX, fromY, toX, toY);
4657         }
4658     }
4659
4660     /* Start the clocks */
4661     whiteFlag = blackFlag = FALSE;
4662     appData.clockMode = !(basetime == 0 && increment == 0);
4663     if (ticking == 0) {
4664       ics_clock_paused = TRUE;
4665       StopClocks();
4666     } else if (ticking == 1) {
4667       ics_clock_paused = FALSE;
4668     }
4669     if (gameMode == IcsIdle ||
4670         relation == RELATION_OBSERVING_STATIC ||
4671         relation == RELATION_EXAMINING ||
4672         ics_clock_paused)
4673       DisplayBothClocks();
4674     else
4675       StartClocks();
4676
4677     /* Display opponents and material strengths */
4678     if (gameInfo.variant != VariantBughouse &&
4679         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4680         if (tinyLayout || smallLayout) {
4681             if(gameInfo.variant == VariantNormal)
4682               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4683                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4684                     basetime, increment);
4685             else
4686               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4687                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4688                     basetime, increment, (int) gameInfo.variant);
4689         } else {
4690             if(gameInfo.variant == VariantNormal)
4691               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4692                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4693                     basetime, increment);
4694             else
4695               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4696                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4697                     basetime, increment, VariantName(gameInfo.variant));
4698         }
4699         DisplayTitle(str);
4700   if (appData.debugMode) {
4701     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4702   }
4703     }
4704
4705
4706     /* Display the board */
4707     if (!pausing && !appData.noGUI) {
4708
4709       if (appData.premove)
4710           if (!gotPremove ||
4711              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4712              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4713               ClearPremoveHighlights();
4714
4715       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4716         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4717       DrawPosition(j, boards[currentMove]);
4718
4719       DisplayMove(moveNum - 1);
4720       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4721             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4722               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4723         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4724       }
4725     }
4726
4727     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4728 #if ZIPPY
4729     if(bookHit) { // [HGM] book: simulate book reply
4730         static char bookMove[MSG_SIZ]; // a bit generous?
4731
4732         programStats.nodes = programStats.depth = programStats.time =
4733         programStats.score = programStats.got_only_move = 0;
4734         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4735
4736         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4737         strcat(bookMove, bookHit);
4738         HandleMachineMove(bookMove, &first);
4739     }
4740 #endif
4741 }
4742
4743 void
4744 GetMoveListEvent()
4745 {
4746     char buf[MSG_SIZ];
4747     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4748         ics_getting_history = H_REQUESTED;
4749         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4750         SendToICS(buf);
4751     }
4752 }
4753
4754 void
4755 AnalysisPeriodicEvent(force)
4756      int force;
4757 {
4758     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4759          && !force) || !appData.periodicUpdates)
4760       return;
4761
4762     /* Send . command to Crafty to collect stats */
4763     SendToProgram(".\n", &first);
4764
4765     /* Don't send another until we get a response (this makes
4766        us stop sending to old Crafty's which don't understand
4767        the "." command (sending illegal cmds resets node count & time,
4768        which looks bad)) */
4769     programStats.ok_to_send = 0;
4770 }
4771
4772 void ics_update_width(new_width)
4773         int new_width;
4774 {
4775         ics_printf("set width %d\n", new_width);
4776 }
4777
4778 void
4779 SendMoveToProgram(moveNum, cps)
4780      int moveNum;
4781      ChessProgramState *cps;
4782 {
4783     char buf[MSG_SIZ];
4784
4785     if (cps->useUsermove) {
4786       SendToProgram("usermove ", cps);
4787     }
4788     if (cps->useSAN) {
4789       char *space;
4790       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4791         int len = space - parseList[moveNum];
4792         memcpy(buf, parseList[moveNum], len);
4793         buf[len++] = '\n';
4794         buf[len] = NULLCHAR;
4795       } else {
4796         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4797       }
4798       SendToProgram(buf, cps);
4799     } else {
4800       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4801         AlphaRank(moveList[moveNum], 4);
4802         SendToProgram(moveList[moveNum], cps);
4803         AlphaRank(moveList[moveNum], 4); // and back
4804       } else
4805       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4806        * the engine. It would be nice to have a better way to identify castle
4807        * moves here. */
4808       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4809                                                                          && cps->useOOCastle) {
4810         int fromX = moveList[moveNum][0] - AAA;
4811         int fromY = moveList[moveNum][1] - ONE;
4812         int toX = moveList[moveNum][2] - AAA;
4813         int toY = moveList[moveNum][3] - ONE;
4814         if((boards[moveNum][fromY][fromX] == WhiteKing
4815             && boards[moveNum][toY][toX] == WhiteRook)
4816            || (boards[moveNum][fromY][fromX] == BlackKing
4817                && boards[moveNum][toY][toX] == BlackRook)) {
4818           if(toX > fromX) SendToProgram("O-O\n", cps);
4819           else SendToProgram("O-O-O\n", cps);
4820         }
4821         else SendToProgram(moveList[moveNum], cps);
4822       }
4823       else SendToProgram(moveList[moveNum], cps);
4824       /* End of additions by Tord */
4825     }
4826
4827     /* [HGM] setting up the opening has brought engine in force mode! */
4828     /*       Send 'go' if we are in a mode where machine should play. */
4829     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4830         (gameMode == TwoMachinesPlay   ||
4831 #if ZIPPY
4832          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4833 #endif
4834          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4835         SendToProgram("go\n", cps);
4836   if (appData.debugMode) {
4837     fprintf(debugFP, "(extra)\n");
4838   }
4839     }
4840     setboardSpoiledMachineBlack = 0;
4841 }
4842
4843 void
4844 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4845      ChessMove moveType;
4846      int fromX, fromY, toX, toY;
4847      char promoChar;
4848 {
4849     char user_move[MSG_SIZ];
4850
4851     switch (moveType) {
4852       default:
4853         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4854                 (int)moveType, fromX, fromY, toX, toY);
4855         DisplayError(user_move + strlen("say "), 0);
4856         break;
4857       case WhiteKingSideCastle:
4858       case BlackKingSideCastle:
4859       case WhiteQueenSideCastleWild:
4860       case BlackQueenSideCastleWild:
4861       /* PUSH Fabien */
4862       case WhiteHSideCastleFR:
4863       case BlackHSideCastleFR:
4864       /* POP Fabien */
4865         snprintf(user_move, MSG_SIZ, "o-o\n");
4866         break;
4867       case WhiteQueenSideCastle:
4868       case BlackQueenSideCastle:
4869       case WhiteKingSideCastleWild:
4870       case BlackKingSideCastleWild:
4871       /* PUSH Fabien */
4872       case WhiteASideCastleFR:
4873       case BlackASideCastleFR:
4874       /* POP Fabien */
4875         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4876         break;
4877       case WhiteNonPromotion:
4878       case BlackNonPromotion:
4879         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4880         break;
4881       case WhitePromotion:
4882       case BlackPromotion:
4883         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4884           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4885                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4886                 PieceToChar(WhiteFerz));
4887         else if(gameInfo.variant == VariantGreat)
4888           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4889                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4890                 PieceToChar(WhiteMan));
4891         else
4892           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4893                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4894                 promoChar);
4895         break;
4896       case WhiteDrop:
4897       case BlackDrop:
4898       drop:
4899         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4900                  ToUpper(PieceToChar((ChessSquare) fromX)),
4901                  AAA + toX, ONE + toY);
4902         break;
4903       case IllegalMove:  /* could be a variant we don't quite understand */
4904         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4905       case NormalMove:
4906       case WhiteCapturesEnPassant:
4907       case BlackCapturesEnPassant:
4908         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4909                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4910         break;
4911     }
4912     SendToICS(user_move);
4913     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4914         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4915 }
4916
4917 void
4918 UploadGameEvent()
4919 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4920     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4921     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4922     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4923         DisplayError("You cannot do this while you are playing or observing", 0);
4924         return;
4925     }
4926     if(gameMode != IcsExamining) { // is this ever not the case?
4927         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4928
4929         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4930           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4931         } else { // on FICS we must first go to general examine mode
4932           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4933         }
4934         if(gameInfo.variant != VariantNormal) {
4935             // try figure out wild number, as xboard names are not always valid on ICS
4936             for(i=1; i<=36; i++) {
4937               snprintf(buf, MSG_SIZ, "wild/%d", i);
4938                 if(StringToVariant(buf) == gameInfo.variant) break;
4939             }
4940             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4941             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4942             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4943         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4944         SendToICS(ics_prefix);
4945         SendToICS(buf);
4946         if(startedFromSetupPosition || backwardMostMove != 0) {
4947           fen = PositionToFEN(backwardMostMove, NULL);
4948           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4949             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4950             SendToICS(buf);
4951           } else { // FICS: everything has to set by separate bsetup commands
4952             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4953             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4954             SendToICS(buf);
4955             if(!WhiteOnMove(backwardMostMove)) {
4956                 SendToICS("bsetup tomove black\n");
4957             }
4958             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4959             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4960             SendToICS(buf);
4961             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4962             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4963             SendToICS(buf);
4964             i = boards[backwardMostMove][EP_STATUS];
4965             if(i >= 0) { // set e.p.
4966               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4967                 SendToICS(buf);
4968             }
4969             bsetup++;
4970           }
4971         }
4972       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4973             SendToICS("bsetup done\n"); // switch to normal examining.
4974     }
4975     for(i = backwardMostMove; i<last; i++) {
4976         char buf[20];
4977         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4978         SendToICS(buf);
4979     }
4980     SendToICS(ics_prefix);
4981     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4982 }
4983
4984 void
4985 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4986      int rf, ff, rt, ft;
4987      char promoChar;
4988      char move[7];
4989 {
4990     if (rf == DROP_RANK) {
4991       sprintf(move, "%c@%c%c\n",
4992                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4993     } else {
4994         if (promoChar == 'x' || promoChar == NULLCHAR) {
4995           sprintf(move, "%c%c%c%c\n",
4996                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4997         } else {
4998             sprintf(move, "%c%c%c%c%c\n",
4999                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5000         }
5001     }
5002 }
5003
5004 void
5005 ProcessICSInitScript(f)
5006      FILE *f;
5007 {
5008     char buf[MSG_SIZ];
5009
5010     while (fgets(buf, MSG_SIZ, f)) {
5011         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5012     }
5013
5014     fclose(f);
5015 }
5016
5017
5018 static int lastX, lastY, selectFlag, dragging;
5019
5020 void
5021 Sweep(int step)
5022 {
5023     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5024     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5025     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5026     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5027     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5028     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5029     do {
5030         promoSweep -= step;
5031         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5032         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5033         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5034         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5035         if(!step) step = 1;
5036     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5037             appData.testLegality && (promoSweep == king ||
5038             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5039     ChangeDragPiece(promoSweep);
5040 }
5041
5042 int PromoScroll(int x, int y)
5043 {
5044   int step = 0;
5045
5046   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5047   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5048   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5049   if(!step) return FALSE;
5050   lastX = x; lastY = y;
5051   if((promoSweep < BlackPawn) == flipView) step = -step;
5052   if(step > 0) selectFlag = 1;
5053   if(!selectFlag) Sweep(step);
5054   return FALSE;
5055 }
5056
5057 void
5058 NextPiece(int step)
5059 {
5060     ChessSquare piece = boards[currentMove][toY][toX];
5061     do {
5062         pieceSweep -= step;
5063         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5064         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5065         if(!step) step = -1;
5066     } while(PieceToChar(pieceSweep) == '.');
5067     boards[currentMove][toY][toX] = pieceSweep;
5068     DrawPosition(FALSE, boards[currentMove]);
5069     boards[currentMove][toY][toX] = piece;
5070 }
5071 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5072 void
5073 AlphaRank(char *move, int n)
5074 {
5075 //    char *p = move, c; int x, y;
5076
5077     if (appData.debugMode) {
5078         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5079     }
5080
5081     if(move[1]=='*' &&
5082        move[2]>='0' && move[2]<='9' &&
5083        move[3]>='a' && move[3]<='x'    ) {
5084         move[1] = '@';
5085         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5086         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5087     } else
5088     if(move[0]>='0' && move[0]<='9' &&
5089        move[1]>='a' && move[1]<='x' &&
5090        move[2]>='0' && move[2]<='9' &&
5091        move[3]>='a' && move[3]<='x'    ) {
5092         /* input move, Shogi -> normal */
5093         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5094         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5095         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5096         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5097     } else
5098     if(move[1]=='@' &&
5099        move[3]>='0' && move[3]<='9' &&
5100        move[2]>='a' && move[2]<='x'    ) {
5101         move[1] = '*';
5102         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5103         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5104     } else
5105     if(
5106        move[0]>='a' && move[0]<='x' &&
5107        move[3]>='0' && move[3]<='9' &&
5108        move[2]>='a' && move[2]<='x'    ) {
5109          /* output move, normal -> Shogi */
5110         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5111         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5112         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5113         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5114         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5115     }
5116     if (appData.debugMode) {
5117         fprintf(debugFP, "   out = '%s'\n", move);
5118     }
5119 }
5120
5121 char yy_textstr[8000];
5122
5123 /* Parser for moves from gnuchess, ICS, or user typein box */
5124 Boolean
5125 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5126      char *move;
5127      int moveNum;
5128      ChessMove *moveType;
5129      int *fromX, *fromY, *toX, *toY;
5130      char *promoChar;
5131 {
5132     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5133
5134     switch (*moveType) {
5135       case WhitePromotion:
5136       case BlackPromotion:
5137       case WhiteNonPromotion:
5138       case BlackNonPromotion:
5139       case NormalMove:
5140       case WhiteCapturesEnPassant:
5141       case BlackCapturesEnPassant:
5142       case WhiteKingSideCastle:
5143       case WhiteQueenSideCastle:
5144       case BlackKingSideCastle:
5145       case BlackQueenSideCastle:
5146       case WhiteKingSideCastleWild:
5147       case WhiteQueenSideCastleWild:
5148       case BlackKingSideCastleWild:
5149       case BlackQueenSideCastleWild:
5150       /* Code added by Tord: */
5151       case WhiteHSideCastleFR:
5152       case WhiteASideCastleFR:
5153       case BlackHSideCastleFR:
5154       case BlackASideCastleFR:
5155       /* End of code added by Tord */
5156       case IllegalMove:         /* bug or odd chess variant */
5157         *fromX = currentMoveString[0] - AAA;
5158         *fromY = currentMoveString[1] - ONE;
5159         *toX = currentMoveString[2] - AAA;
5160         *toY = currentMoveString[3] - ONE;
5161         *promoChar = currentMoveString[4];
5162         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5163             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5164     if (appData.debugMode) {
5165         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5166     }
5167             *fromX = *fromY = *toX = *toY = 0;
5168             return FALSE;
5169         }
5170         if (appData.testLegality) {
5171           return (*moveType != IllegalMove);
5172         } else {
5173           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5174                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5175         }
5176
5177       case WhiteDrop:
5178       case BlackDrop:
5179         *fromX = *moveType == WhiteDrop ?
5180           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5181           (int) CharToPiece(ToLower(currentMoveString[0]));
5182         *fromY = DROP_RANK;
5183         *toX = currentMoveString[2] - AAA;
5184         *toY = currentMoveString[3] - ONE;
5185         *promoChar = NULLCHAR;
5186         return TRUE;
5187
5188       case AmbiguousMove:
5189       case ImpossibleMove:
5190       case EndOfFile:
5191       case ElapsedTime:
5192       case Comment:
5193       case PGNTag:
5194       case NAG:
5195       case WhiteWins:
5196       case BlackWins:
5197       case GameIsDrawn:
5198       default:
5199     if (appData.debugMode) {
5200         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5201     }
5202         /* bug? */
5203         *fromX = *fromY = *toX = *toY = 0;
5204         *promoChar = NULLCHAR;
5205         return FALSE;
5206     }
5207 }
5208
5209
5210 void
5211 ParsePV(char *pv, Boolean storeComments)
5212 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5213   int fromX, fromY, toX, toY; char promoChar;
5214   ChessMove moveType;
5215   Boolean valid;
5216   int nr = 0;
5217
5218   endPV = forwardMostMove;
5219   do {
5220     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5221     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5222     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5223 if(appData.debugMode){
5224 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);
5225 }
5226     if(!valid && nr == 0 &&
5227        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5228         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5229         // Hande case where played move is different from leading PV move
5230         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5231         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5232         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5233         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5234           endPV += 2; // if position different, keep this
5235           moveList[endPV-1][0] = fromX + AAA;
5236           moveList[endPV-1][1] = fromY + ONE;
5237           moveList[endPV-1][2] = toX + AAA;
5238           moveList[endPV-1][3] = toY + ONE;
5239           parseList[endPV-1][0] = NULLCHAR;
5240           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5241         }
5242       }
5243     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5244     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5245     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5246     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5247         valid++; // allow comments in PV
5248         continue;
5249     }
5250     nr++;
5251     if(endPV+1 > framePtr) break; // no space, truncate
5252     if(!valid) break;
5253     endPV++;
5254     CopyBoard(boards[endPV], boards[endPV-1]);
5255     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5256     moveList[endPV-1][0] = fromX + AAA;
5257     moveList[endPV-1][1] = fromY + ONE;
5258     moveList[endPV-1][2] = toX + AAA;
5259     moveList[endPV-1][3] = toY + ONE;
5260     moveList[endPV-1][4] = promoChar;
5261     moveList[endPV-1][5] = NULLCHAR;
5262     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5263     if(storeComments)
5264         CoordsToAlgebraic(boards[endPV - 1],
5265                              PosFlags(endPV - 1),
5266                              fromY, fromX, toY, toX, promoChar,
5267                              parseList[endPV - 1]);
5268     else
5269         parseList[endPV-1][0] = NULLCHAR;
5270   } while(valid);
5271   currentMove = endPV;
5272   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5273   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5274                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5275   DrawPosition(TRUE, boards[currentMove]);
5276 }
5277
5278 Boolean
5279 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5280 {
5281         int startPV;
5282         char *p;
5283
5284         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5285         lastX = x; lastY = y;
5286         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5287         startPV = index;
5288         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5289         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5290         index = startPV;
5291         do{ while(buf[index] && buf[index] != '\n') index++;
5292         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5293         buf[index] = 0;
5294         ParsePV(buf+startPV, FALSE);
5295         *start = startPV; *end = index-1;
5296         return TRUE;
5297 }
5298
5299 Boolean
5300 LoadPV(int x, int y)
5301 { // called on right mouse click to load PV
5302   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5303   lastX = x; lastY = y;
5304   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5305   return TRUE;
5306 }
5307
5308 void
5309 UnLoadPV()
5310 {
5311   if(endPV < 0) return;
5312   endPV = -1;
5313   currentMove = forwardMostMove;
5314   ClearPremoveHighlights();
5315   DrawPosition(TRUE, boards[currentMove]);
5316 }
5317
5318 void
5319 MovePV(int x, int y, int h)
5320 { // step through PV based on mouse coordinates (called on mouse move)
5321   int margin = h>>3, step = 0;
5322
5323   // we must somehow check if right button is still down (might be released off board!)
5324   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5325   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5326   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5327   if(!step) return;
5328   lastX = x; lastY = y;
5329
5330   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5331   if(endPV < 0) return;
5332   if(y < margin) step = 1; else
5333   if(y > h - margin) step = -1;
5334   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5335   currentMove += step;
5336   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5337   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5338                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5339   DrawPosition(FALSE, boards[currentMove]);
5340 }
5341
5342
5343 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5344 // All positions will have equal probability, but the current method will not provide a unique
5345 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5346 #define DARK 1
5347 #define LITE 2
5348 #define ANY 3
5349
5350 int squaresLeft[4];
5351 int piecesLeft[(int)BlackPawn];
5352 int seed, nrOfShuffles;
5353
5354 void GetPositionNumber()
5355 {       // sets global variable seed
5356         int i;
5357
5358         seed = appData.defaultFrcPosition;
5359         if(seed < 0) { // randomize based on time for negative FRC position numbers
5360                 for(i=0; i<50; i++) seed += random();
5361                 seed = random() ^ random() >> 8 ^ random() << 8;
5362                 if(seed<0) seed = -seed;
5363         }
5364 }
5365
5366 int put(Board board, int pieceType, int rank, int n, int shade)
5367 // put the piece on the (n-1)-th empty squares of the given shade
5368 {
5369         int i;
5370
5371         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5372                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5373                         board[rank][i] = (ChessSquare) pieceType;
5374                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5375                         squaresLeft[ANY]--;
5376                         piecesLeft[pieceType]--;
5377                         return i;
5378                 }
5379         }
5380         return -1;
5381 }
5382
5383
5384 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5385 // calculate where the next piece goes, (any empty square), and put it there
5386 {
5387         int i;
5388
5389         i = seed % squaresLeft[shade];
5390         nrOfShuffles *= squaresLeft[shade];
5391         seed /= squaresLeft[shade];
5392         put(board, pieceType, rank, i, shade);
5393 }
5394
5395 void AddTwoPieces(Board board, int pieceType, int rank)
5396 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5397 {
5398         int i, n=squaresLeft[ANY], j=n-1, k;
5399
5400         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5401         i = seed % k;  // pick one
5402         nrOfShuffles *= k;
5403         seed /= k;
5404         while(i >= j) i -= j--;
5405         j = n - 1 - j; i += j;
5406         put(board, pieceType, rank, j, ANY);
5407         put(board, pieceType, rank, i, ANY);
5408 }
5409
5410 void SetUpShuffle(Board board, int number)
5411 {
5412         int i, p, first=1;
5413
5414         GetPositionNumber(); nrOfShuffles = 1;
5415
5416         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5417         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5418         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5419
5420         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5421
5422         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5423             p = (int) board[0][i];
5424             if(p < (int) BlackPawn) piecesLeft[p] ++;
5425             board[0][i] = EmptySquare;
5426         }
5427
5428         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5429             // shuffles restricted to allow normal castling put KRR first
5430             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5431                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5432             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5433                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5434             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5435                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5436             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5437                 put(board, WhiteRook, 0, 0, ANY);
5438             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5439         }
5440
5441         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5442             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5443             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5444                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5445                 while(piecesLeft[p] >= 2) {
5446                     AddOnePiece(board, p, 0, LITE);
5447                     AddOnePiece(board, p, 0, DARK);
5448                 }
5449                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5450             }
5451
5452         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5453             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5454             // but we leave King and Rooks for last, to possibly obey FRC restriction
5455             if(p == (int)WhiteRook) continue;
5456             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5457             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5458         }
5459
5460         // now everything is placed, except perhaps King (Unicorn) and Rooks
5461
5462         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5463             // Last King gets castling rights
5464             while(piecesLeft[(int)WhiteUnicorn]) {
5465                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5466                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5467             }
5468
5469             while(piecesLeft[(int)WhiteKing]) {
5470                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5471                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5472             }
5473
5474
5475         } else {
5476             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5477             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5478         }
5479
5480         // Only Rooks can be left; simply place them all
5481         while(piecesLeft[(int)WhiteRook]) {
5482                 i = put(board, WhiteRook, 0, 0, ANY);
5483                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5484                         if(first) {
5485                                 first=0;
5486                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5487                         }
5488                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5489                 }
5490         }
5491         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5492             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5493         }
5494
5495         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5496 }
5497
5498 int SetCharTable( char *table, const char * map )
5499 /* [HGM] moved here from winboard.c because of its general usefulness */
5500 /*       Basically a safe strcpy that uses the last character as King */
5501 {
5502     int result = FALSE; int NrPieces;
5503
5504     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5505                     && NrPieces >= 12 && !(NrPieces&1)) {
5506         int i; /* [HGM] Accept even length from 12 to 34 */
5507
5508         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5509         for( i=0; i<NrPieces/2-1; i++ ) {
5510             table[i] = map[i];
5511             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5512         }
5513         table[(int) WhiteKing]  = map[NrPieces/2-1];
5514         table[(int) BlackKing]  = map[NrPieces-1];
5515
5516         result = TRUE;
5517     }
5518
5519     return result;
5520 }
5521
5522 void Prelude(Board board)
5523 {       // [HGM] superchess: random selection of exo-pieces
5524         int i, j, k; ChessSquare p;
5525         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5526
5527         GetPositionNumber(); // use FRC position number
5528
5529         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5530             SetCharTable(pieceToChar, appData.pieceToCharTable);
5531             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5532                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5533         }
5534
5535         j = seed%4;                 seed /= 4;
5536         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5537         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5538         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5539         j = seed%3 + (seed%3 >= j); seed /= 3;
5540         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5541         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5542         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5543         j = seed%3;                 seed /= 3;
5544         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5545         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5546         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5547         j = seed%2 + (seed%2 >= j); seed /= 2;
5548         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5549         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5550         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5551         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5552         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5553         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5554         put(board, exoPieces[0],    0, 0, ANY);
5555         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5556 }
5557
5558 void
5559 InitPosition(redraw)
5560      int redraw;
5561 {
5562     ChessSquare (* pieces)[BOARD_FILES];
5563     int i, j, pawnRow, overrule,
5564     oldx = gameInfo.boardWidth,
5565     oldy = gameInfo.boardHeight,
5566     oldh = gameInfo.holdingsWidth;
5567     static int oldv;
5568
5569     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5570
5571     /* [AS] Initialize pv info list [HGM] and game status */
5572     {
5573         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5574             pvInfoList[i].depth = 0;
5575             boards[i][EP_STATUS] = EP_NONE;
5576             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5577         }
5578
5579         initialRulePlies = 0; /* 50-move counter start */
5580
5581         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5582         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5583     }
5584
5585
5586     /* [HGM] logic here is completely changed. In stead of full positions */
5587     /* the initialized data only consist of the two backranks. The switch */
5588     /* selects which one we will use, which is than copied to the Board   */
5589     /* initialPosition, which for the rest is initialized by Pawns and    */
5590     /* empty squares. This initial position is then copied to boards[0],  */
5591     /* possibly after shuffling, so that it remains available.            */
5592
5593     gameInfo.holdingsWidth = 0; /* default board sizes */
5594     gameInfo.boardWidth    = 8;
5595     gameInfo.boardHeight   = 8;
5596     gameInfo.holdingsSize  = 0;
5597     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5598     for(i=0; i<BOARD_FILES-2; i++)
5599       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5600     initialPosition[EP_STATUS] = EP_NONE;
5601     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5602     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5603          SetCharTable(pieceNickName, appData.pieceNickNames);
5604     else SetCharTable(pieceNickName, "............");
5605     pieces = FIDEArray;
5606
5607     switch (gameInfo.variant) {
5608     case VariantFischeRandom:
5609       shuffleOpenings = TRUE;
5610     default:
5611       break;
5612     case VariantShatranj:
5613       pieces = ShatranjArray;
5614       nrCastlingRights = 0;
5615       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5616       break;
5617     case VariantMakruk:
5618       pieces = makrukArray;
5619       nrCastlingRights = 0;
5620       startedFromSetupPosition = TRUE;
5621       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5622       break;
5623     case VariantTwoKings:
5624       pieces = twoKingsArray;
5625       break;
5626     case VariantCapaRandom:
5627       shuffleOpenings = TRUE;
5628     case VariantCapablanca:
5629       pieces = CapablancaArray;
5630       gameInfo.boardWidth = 10;
5631       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5632       break;
5633     case VariantGothic:
5634       pieces = GothicArray;
5635       gameInfo.boardWidth = 10;
5636       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5637       break;
5638     case VariantSChess:
5639       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5640       gameInfo.holdingsSize = 7;
5641       break;
5642     case VariantJanus:
5643       pieces = JanusArray;
5644       gameInfo.boardWidth = 10;
5645       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5646       nrCastlingRights = 6;
5647         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5648         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5649         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5650         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5651         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5652         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5653       break;
5654     case VariantFalcon:
5655       pieces = FalconArray;
5656       gameInfo.boardWidth = 10;
5657       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5658       break;
5659     case VariantXiangqi:
5660       pieces = XiangqiArray;
5661       gameInfo.boardWidth  = 9;
5662       gameInfo.boardHeight = 10;
5663       nrCastlingRights = 0;
5664       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5665       break;
5666     case VariantShogi:
5667       pieces = ShogiArray;
5668       gameInfo.boardWidth  = 9;
5669       gameInfo.boardHeight = 9;
5670       gameInfo.holdingsSize = 7;
5671       nrCastlingRights = 0;
5672       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5673       break;
5674     case VariantCourier:
5675       pieces = CourierArray;
5676       gameInfo.boardWidth  = 12;
5677       nrCastlingRights = 0;
5678       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5679       break;
5680     case VariantKnightmate:
5681       pieces = KnightmateArray;
5682       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5683       break;
5684     case VariantSpartan:
5685       pieces = SpartanArray;
5686       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5687       break;
5688     case VariantFairy:
5689       pieces = fairyArray;
5690       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5691       break;
5692     case VariantGreat:
5693       pieces = GreatArray;
5694       gameInfo.boardWidth = 10;
5695       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5696       gameInfo.holdingsSize = 8;
5697       break;
5698     case VariantSuper:
5699       pieces = FIDEArray;
5700       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5701       gameInfo.holdingsSize = 8;
5702       startedFromSetupPosition = TRUE;
5703       break;
5704     case VariantCrazyhouse:
5705     case VariantBughouse:
5706       pieces = FIDEArray;
5707       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5708       gameInfo.holdingsSize = 5;
5709       break;
5710     case VariantWildCastle:
5711       pieces = FIDEArray;
5712       /* !!?shuffle with kings guaranteed to be on d or e file */
5713       shuffleOpenings = 1;
5714       break;
5715     case VariantNoCastle:
5716       pieces = FIDEArray;
5717       nrCastlingRights = 0;
5718       /* !!?unconstrained back-rank shuffle */
5719       shuffleOpenings = 1;
5720       break;
5721     }
5722
5723     overrule = 0;
5724     if(appData.NrFiles >= 0) {
5725         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5726         gameInfo.boardWidth = appData.NrFiles;
5727     }
5728     if(appData.NrRanks >= 0) {
5729         gameInfo.boardHeight = appData.NrRanks;
5730     }
5731     if(appData.holdingsSize >= 0) {
5732         i = appData.holdingsSize;
5733         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5734         gameInfo.holdingsSize = i;
5735     }
5736     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5737     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5738         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5739
5740     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5741     if(pawnRow < 1) pawnRow = 1;
5742     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5743
5744     /* User pieceToChar list overrules defaults */
5745     if(appData.pieceToCharTable != NULL)
5746         SetCharTable(pieceToChar, appData.pieceToCharTable);
5747
5748     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5749
5750         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5751             s = (ChessSquare) 0; /* account holding counts in guard band */
5752         for( i=0; i<BOARD_HEIGHT; i++ )
5753             initialPosition[i][j] = s;
5754
5755         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5756         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5757         initialPosition[pawnRow][j] = WhitePawn;
5758         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5759         if(gameInfo.variant == VariantXiangqi) {
5760             if(j&1) {
5761                 initialPosition[pawnRow][j] =
5762                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5763                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5764                    initialPosition[2][j] = WhiteCannon;
5765                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5766                 }
5767             }
5768         }
5769         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5770     }
5771     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5772
5773             j=BOARD_LEFT+1;
5774             initialPosition[1][j] = WhiteBishop;
5775             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5776             j=BOARD_RGHT-2;
5777             initialPosition[1][j] = WhiteRook;
5778             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5779     }
5780
5781     if( nrCastlingRights == -1) {
5782         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5783         /*       This sets default castling rights from none to normal corners   */
5784         /* Variants with other castling rights must set them themselves above    */
5785         nrCastlingRights = 6;
5786
5787         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5788         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5789         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5790         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5791         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5792         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5793      }
5794
5795      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5796      if(gameInfo.variant == VariantGreat) { // promotion commoners
5797         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5798         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5799         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5800         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5801      }
5802      if( gameInfo.variant == VariantSChess ) {
5803       initialPosition[1][0] = BlackMarshall;
5804       initialPosition[2][0] = BlackAngel;
5805       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5806       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5807       initialPosition[1][1] = initialPosition[2][1] = 
5808       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5809      }
5810   if (appData.debugMode) {
5811     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5812   }
5813     if(shuffleOpenings) {
5814         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5815         startedFromSetupPosition = TRUE;
5816     }
5817     if(startedFromPositionFile) {
5818       /* [HGM] loadPos: use PositionFile for every new game */
5819       CopyBoard(initialPosition, filePosition);
5820       for(i=0; i<nrCastlingRights; i++)
5821           initialRights[i] = filePosition[CASTLING][i];
5822       startedFromSetupPosition = TRUE;
5823     }
5824
5825     CopyBoard(boards[0], initialPosition);
5826
5827     if(oldx != gameInfo.boardWidth ||
5828        oldy != gameInfo.boardHeight ||
5829        oldv != gameInfo.variant ||
5830        oldh != gameInfo.holdingsWidth
5831                                          )
5832             InitDrawingSizes(-2 ,0);
5833
5834     oldv = gameInfo.variant;
5835     if (redraw)
5836       DrawPosition(TRUE, boards[currentMove]);
5837 }
5838
5839 void
5840 SendBoard(cps, moveNum)
5841      ChessProgramState *cps;
5842      int moveNum;
5843 {
5844     char message[MSG_SIZ];
5845
5846     if (cps->useSetboard) {
5847       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5848       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5849       SendToProgram(message, cps);
5850       free(fen);
5851
5852     } else {
5853       ChessSquare *bp;
5854       int i, j;
5855       /* Kludge to set black to move, avoiding the troublesome and now
5856        * deprecated "black" command.
5857        */
5858       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5859         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5860
5861       SendToProgram("edit\n", cps);
5862       SendToProgram("#\n", cps);
5863       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5864         bp = &boards[moveNum][i][BOARD_LEFT];
5865         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5866           if ((int) *bp < (int) BlackPawn) {
5867             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5868                     AAA + j, ONE + i);
5869             if(message[0] == '+' || message[0] == '~') {
5870               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5871                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5872                         AAA + j, ONE + i);
5873             }
5874             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5875                 message[1] = BOARD_RGHT   - 1 - j + '1';
5876                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5877             }
5878             SendToProgram(message, cps);
5879           }
5880         }
5881       }
5882
5883       SendToProgram("c\n", cps);
5884       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5885         bp = &boards[moveNum][i][BOARD_LEFT];
5886         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5887           if (((int) *bp != (int) EmptySquare)
5888               && ((int) *bp >= (int) BlackPawn)) {
5889             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5890                     AAA + j, ONE + i);
5891             if(message[0] == '+' || message[0] == '~') {
5892               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5893                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5894                         AAA + j, ONE + i);
5895             }
5896             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5897                 message[1] = BOARD_RGHT   - 1 - j + '1';
5898                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5899             }
5900             SendToProgram(message, cps);
5901           }
5902         }
5903       }
5904
5905       SendToProgram(".\n", cps);
5906     }
5907     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5908 }
5909
5910 ChessSquare
5911 DefaultPromoChoice(int white)
5912 {
5913     ChessSquare result;
5914     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5915         result = WhiteFerz; // no choice
5916     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5917         result= WhiteKing; // in Suicide Q is the last thing we want
5918     else if(gameInfo.variant == VariantSpartan)
5919         result = white ? WhiteQueen : WhiteAngel;
5920     else result = WhiteQueen;
5921     if(!white) result = WHITE_TO_BLACK result;
5922     return result;
5923 }
5924
5925 static int autoQueen; // [HGM] oneclick
5926
5927 int
5928 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5929 {
5930     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5931     /* [HGM] add Shogi promotions */
5932     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5933     ChessSquare piece;
5934     ChessMove moveType;
5935     Boolean premove;
5936
5937     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5938     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5939
5940     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5941       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5942         return FALSE;
5943
5944     piece = boards[currentMove][fromY][fromX];
5945     if(gameInfo.variant == VariantShogi) {
5946         promotionZoneSize = BOARD_HEIGHT/3;
5947         highestPromotingPiece = (int)WhiteFerz;
5948     } else if(gameInfo.variant == VariantMakruk) {
5949         promotionZoneSize = 3;
5950     }
5951
5952     // Treat Lance as Pawn when it is not representing Amazon
5953     if(gameInfo.variant != VariantSuper) {
5954         if(piece == WhiteLance) piece = WhitePawn; else
5955         if(piece == BlackLance) piece = BlackPawn;
5956     }
5957
5958     // next weed out all moves that do not touch the promotion zone at all
5959     if((int)piece >= BlackPawn) {
5960         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5961              return FALSE;
5962         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5963     } else {
5964         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5965            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5966     }
5967
5968     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5969
5970     // weed out mandatory Shogi promotions
5971     if(gameInfo.variant == VariantShogi) {
5972         if(piece >= BlackPawn) {
5973             if(toY == 0 && piece == BlackPawn ||
5974                toY == 0 && piece == BlackQueen ||
5975                toY <= 1 && piece == BlackKnight) {
5976                 *promoChoice = '+';
5977                 return FALSE;
5978             }
5979         } else {
5980             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5981                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5982                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5983                 *promoChoice = '+';
5984                 return FALSE;
5985             }
5986         }
5987     }
5988
5989     // weed out obviously illegal Pawn moves
5990     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5991         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5992         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5993         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5994         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5995         // note we are not allowed to test for valid (non-)capture, due to premove
5996     }
5997
5998     // we either have a choice what to promote to, or (in Shogi) whether to promote
5999     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6000         *promoChoice = PieceToChar(BlackFerz);  // no choice
6001         return FALSE;
6002     }
6003     // no sense asking what we must promote to if it is going to explode...
6004     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6005         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6006         return FALSE;
6007     }
6008     // give caller the default choice even if we will not make it
6009     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6010     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6011     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6012                            && gameInfo.variant != VariantShogi
6013                            && gameInfo.variant != VariantSuper) return FALSE;
6014     if(autoQueen) return FALSE; // predetermined
6015
6016     // suppress promotion popup on illegal moves that are not premoves
6017     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6018               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6019     if(appData.testLegality && !premove) {
6020         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6021                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6022         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6023             return FALSE;
6024     }
6025
6026     return TRUE;
6027 }
6028
6029 int
6030 InPalace(row, column)
6031      int row, column;
6032 {   /* [HGM] for Xiangqi */
6033     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6034          column < (BOARD_WIDTH + 4)/2 &&
6035          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6036     return FALSE;
6037 }
6038
6039 int
6040 PieceForSquare (x, y)
6041      int x;
6042      int y;
6043 {
6044   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6045      return -1;
6046   else
6047      return boards[currentMove][y][x];
6048 }
6049
6050 int
6051 OKToStartUserMove(x, y)
6052      int x, y;
6053 {
6054     ChessSquare from_piece;
6055     int white_piece;
6056
6057     if (matchMode) return FALSE;
6058     if (gameMode == EditPosition) return TRUE;
6059
6060     if (x >= 0 && y >= 0)
6061       from_piece = boards[currentMove][y][x];
6062     else
6063       from_piece = EmptySquare;
6064
6065     if (from_piece == EmptySquare) return FALSE;
6066
6067     white_piece = (int)from_piece >= (int)WhitePawn &&
6068       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6069
6070     switch (gameMode) {
6071       case PlayFromGameFile:
6072       case AnalyzeFile:
6073       case TwoMachinesPlay:
6074       case EndOfGame:
6075         return FALSE;
6076
6077       case IcsObserving:
6078       case IcsIdle:
6079         return FALSE;
6080
6081       case MachinePlaysWhite:
6082       case IcsPlayingBlack:
6083         if (appData.zippyPlay) return FALSE;
6084         if (white_piece) {
6085             DisplayMoveError(_("You are playing Black"));
6086             return FALSE;
6087         }
6088         break;
6089
6090       case MachinePlaysBlack:
6091       case IcsPlayingWhite:
6092         if (appData.zippyPlay) return FALSE;
6093         if (!white_piece) {
6094             DisplayMoveError(_("You are playing White"));
6095             return FALSE;
6096         }
6097         break;
6098
6099       case EditGame:
6100         if (!white_piece && WhiteOnMove(currentMove)) {
6101             DisplayMoveError(_("It is White's turn"));
6102             return FALSE;
6103         }
6104         if (white_piece && !WhiteOnMove(currentMove)) {
6105             DisplayMoveError(_("It is Black's turn"));
6106             return FALSE;
6107         }
6108         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6109             /* Editing correspondence game history */
6110             /* Could disallow this or prompt for confirmation */
6111             cmailOldMove = -1;
6112         }
6113         break;
6114
6115       case BeginningOfGame:
6116         if (appData.icsActive) return FALSE;
6117         if (!appData.noChessProgram) {
6118             if (!white_piece) {
6119                 DisplayMoveError(_("You are playing White"));
6120                 return FALSE;
6121             }
6122         }
6123         break;
6124
6125       case Training:
6126         if (!white_piece && WhiteOnMove(currentMove)) {
6127             DisplayMoveError(_("It is White's turn"));
6128             return FALSE;
6129         }
6130         if (white_piece && !WhiteOnMove(currentMove)) {
6131             DisplayMoveError(_("It is Black's turn"));
6132             return FALSE;
6133         }
6134         break;
6135
6136       default:
6137       case IcsExamining:
6138         break;
6139     }
6140     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6141         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6142         && gameMode != AnalyzeFile && gameMode != Training) {
6143         DisplayMoveError(_("Displayed position is not current"));
6144         return FALSE;
6145     }
6146     return TRUE;
6147 }
6148
6149 Boolean
6150 OnlyMove(int *x, int *y, Boolean captures) {
6151     DisambiguateClosure cl;
6152     if (appData.zippyPlay) return FALSE;
6153     switch(gameMode) {
6154       case MachinePlaysBlack:
6155       case IcsPlayingWhite:
6156       case BeginningOfGame:
6157         if(!WhiteOnMove(currentMove)) return FALSE;
6158         break;
6159       case MachinePlaysWhite:
6160       case IcsPlayingBlack:
6161         if(WhiteOnMove(currentMove)) return FALSE;
6162         break;
6163       case EditGame:
6164         break;
6165       default:
6166         return FALSE;
6167     }
6168     cl.pieceIn = EmptySquare;
6169     cl.rfIn = *y;
6170     cl.ffIn = *x;
6171     cl.rtIn = -1;
6172     cl.ftIn = -1;
6173     cl.promoCharIn = NULLCHAR;
6174     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6175     if( cl.kind == NormalMove ||
6176         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6177         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6178         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6179       fromX = cl.ff;
6180       fromY = cl.rf;
6181       *x = cl.ft;
6182       *y = cl.rt;
6183       return TRUE;
6184     }
6185     if(cl.kind != ImpossibleMove) return FALSE;
6186     cl.pieceIn = EmptySquare;
6187     cl.rfIn = -1;
6188     cl.ffIn = -1;
6189     cl.rtIn = *y;
6190     cl.ftIn = *x;
6191     cl.promoCharIn = NULLCHAR;
6192     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6193     if( cl.kind == NormalMove ||
6194         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6195         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6196         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6197       fromX = cl.ff;
6198       fromY = cl.rf;
6199       *x = cl.ft;
6200       *y = cl.rt;
6201       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6202       return TRUE;
6203     }
6204     return FALSE;
6205 }
6206
6207 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6208 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6209 int lastLoadGameUseList = FALSE;
6210 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6211 ChessMove lastLoadGameStart = EndOfFile;
6212
6213 void
6214 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6215      int fromX, fromY, toX, toY;
6216      int promoChar;
6217 {
6218     ChessMove moveType;
6219     ChessSquare pdown, pup;
6220
6221     /* Check if the user is playing in turn.  This is complicated because we
6222        let the user "pick up" a piece before it is his turn.  So the piece he
6223        tried to pick up may have been captured by the time he puts it down!
6224        Therefore we use the color the user is supposed to be playing in this
6225        test, not the color of the piece that is currently on the starting
6226        square---except in EditGame mode, where the user is playing both
6227        sides; fortunately there the capture race can't happen.  (It can
6228        now happen in IcsExamining mode, but that's just too bad.  The user
6229        will get a somewhat confusing message in that case.)
6230        */
6231
6232     switch (gameMode) {
6233       case PlayFromGameFile:
6234       case AnalyzeFile:
6235       case TwoMachinesPlay:
6236       case EndOfGame:
6237       case IcsObserving:
6238       case IcsIdle:
6239         /* We switched into a game mode where moves are not accepted,
6240            perhaps while the mouse button was down. */
6241         return;
6242
6243       case MachinePlaysWhite:
6244         /* User is moving for Black */
6245         if (WhiteOnMove(currentMove)) {
6246             DisplayMoveError(_("It is White's turn"));
6247             return;
6248         }
6249         break;
6250
6251       case MachinePlaysBlack:
6252         /* User is moving for White */
6253         if (!WhiteOnMove(currentMove)) {
6254             DisplayMoveError(_("It is Black's turn"));
6255             return;
6256         }
6257         break;
6258
6259       case EditGame:
6260       case IcsExamining:
6261       case BeginningOfGame:
6262       case AnalyzeMode:
6263       case Training:
6264         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6265         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6266             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6267             /* User is moving for Black */
6268             if (WhiteOnMove(currentMove)) {
6269                 DisplayMoveError(_("It is White's turn"));
6270                 return;
6271             }
6272         } else {
6273             /* User is moving for White */
6274             if (!WhiteOnMove(currentMove)) {
6275                 DisplayMoveError(_("It is Black's turn"));
6276                 return;
6277             }
6278         }
6279         break;
6280
6281       case IcsPlayingBlack:
6282         /* User is moving for Black */
6283         if (WhiteOnMove(currentMove)) {
6284             if (!appData.premove) {
6285                 DisplayMoveError(_("It is White's turn"));
6286             } else if (toX >= 0 && toY >= 0) {
6287                 premoveToX = toX;
6288                 premoveToY = toY;
6289                 premoveFromX = fromX;
6290                 premoveFromY = fromY;
6291                 premovePromoChar = promoChar;
6292                 gotPremove = 1;
6293                 if (appData.debugMode)
6294                     fprintf(debugFP, "Got premove: fromX %d,"
6295                             "fromY %d, toX %d, toY %d\n",
6296                             fromX, fromY, toX, toY);
6297             }
6298             return;
6299         }
6300         break;
6301
6302       case IcsPlayingWhite:
6303         /* User is moving for White */
6304         if (!WhiteOnMove(currentMove)) {
6305             if (!appData.premove) {
6306                 DisplayMoveError(_("It is Black's turn"));
6307             } else if (toX >= 0 && toY >= 0) {
6308                 premoveToX = toX;
6309                 premoveToY = toY;
6310                 premoveFromX = fromX;
6311                 premoveFromY = fromY;
6312                 premovePromoChar = promoChar;
6313                 gotPremove = 1;
6314                 if (appData.debugMode)
6315                     fprintf(debugFP, "Got premove: fromX %d,"
6316                             "fromY %d, toX %d, toY %d\n",
6317                             fromX, fromY, toX, toY);
6318             }
6319             return;
6320         }
6321         break;
6322
6323       default:
6324         break;
6325
6326       case EditPosition:
6327         /* EditPosition, empty square, or different color piece;
6328            click-click move is possible */
6329         if (toX == -2 || toY == -2) {
6330             boards[0][fromY][fromX] = EmptySquare;
6331             DrawPosition(FALSE, boards[currentMove]);
6332             return;
6333         } else if (toX >= 0 && toY >= 0) {
6334             boards[0][toY][toX] = boards[0][fromY][fromX];
6335             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6336                 if(boards[0][fromY][0] != EmptySquare) {
6337                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6338                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6339                 }
6340             } else
6341             if(fromX == BOARD_RGHT+1) {
6342                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6343                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6344                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6345                 }
6346             } else
6347             boards[0][fromY][fromX] = EmptySquare;
6348             DrawPosition(FALSE, boards[currentMove]);
6349             return;
6350         }
6351         return;
6352     }
6353
6354     if(toX < 0 || toY < 0) return;
6355     pdown = boards[currentMove][fromY][fromX];
6356     pup = boards[currentMove][toY][toX];
6357
6358     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6359     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6360          if( pup != EmptySquare ) return;
6361          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6362            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6363                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6364            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6365            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6366            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6367            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6368          fromY = DROP_RANK;
6369     }
6370
6371     /* [HGM] always test for legality, to get promotion info */
6372     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6373                                          fromY, fromX, toY, toX, promoChar);
6374     /* [HGM] but possibly ignore an IllegalMove result */
6375     if (appData.testLegality) {
6376         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6377             DisplayMoveError(_("Illegal move"));
6378             return;
6379         }
6380     }
6381
6382     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6383 }
6384
6385 /* Common tail of UserMoveEvent and DropMenuEvent */
6386 int
6387 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6388      ChessMove moveType;
6389      int fromX, fromY, toX, toY;
6390      /*char*/int promoChar;
6391 {
6392     char *bookHit = 0;
6393
6394     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6395         // [HGM] superchess: suppress promotions to non-available piece
6396         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6397         if(WhiteOnMove(currentMove)) {
6398             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6399         } else {
6400             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6401         }
6402     }
6403
6404     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6405        move type in caller when we know the move is a legal promotion */
6406     if(moveType == NormalMove && promoChar)
6407         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6408
6409     /* [HGM] <popupFix> The following if has been moved here from
6410        UserMoveEvent(). Because it seemed to belong here (why not allow
6411        piece drops in training games?), and because it can only be
6412        performed after it is known to what we promote. */
6413     if (gameMode == Training) {
6414       /* compare the move played on the board to the next move in the
6415        * game. If they match, display the move and the opponent's response.
6416        * If they don't match, display an error message.
6417        */
6418       int saveAnimate;
6419       Board testBoard;
6420       CopyBoard(testBoard, boards[currentMove]);
6421       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6422
6423       if (CompareBoards(testBoard, boards[currentMove+1])) {
6424         ForwardInner(currentMove+1);
6425
6426         /* Autoplay the opponent's response.
6427          * if appData.animate was TRUE when Training mode was entered,
6428          * the response will be animated.
6429          */
6430         saveAnimate = appData.animate;
6431         appData.animate = animateTraining;
6432         ForwardInner(currentMove+1);
6433         appData.animate = saveAnimate;
6434
6435         /* check for the end of the game */
6436         if (currentMove >= forwardMostMove) {
6437           gameMode = PlayFromGameFile;
6438           ModeHighlight();
6439           SetTrainingModeOff();
6440           DisplayInformation(_("End of game"));
6441         }
6442       } else {
6443         DisplayError(_("Incorrect move"), 0);
6444       }
6445       return 1;
6446     }
6447
6448   /* Ok, now we know that the move is good, so we can kill
6449      the previous line in Analysis Mode */
6450   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6451                                 && currentMove < forwardMostMove) {
6452     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6453     else forwardMostMove = currentMove;
6454   }
6455
6456   /* If we need the chess program but it's dead, restart it */
6457   ResurrectChessProgram();
6458
6459   /* A user move restarts a paused game*/
6460   if (pausing)
6461     PauseEvent();
6462
6463   thinkOutput[0] = NULLCHAR;
6464
6465   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6466
6467   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6468     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6469     return 1;
6470   }
6471
6472   if (gameMode == BeginningOfGame) {
6473     if (appData.noChessProgram) {
6474       gameMode = EditGame;
6475       SetGameInfo();
6476     } else {
6477       char buf[MSG_SIZ];
6478       gameMode = MachinePlaysBlack;
6479       StartClocks();
6480       SetGameInfo();
6481       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6482       DisplayTitle(buf);
6483       if (first.sendName) {
6484         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6485         SendToProgram(buf, &first);
6486       }
6487       StartClocks();
6488     }
6489     ModeHighlight();
6490   }
6491
6492   /* Relay move to ICS or chess engine */
6493   if (appData.icsActive) {
6494     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6495         gameMode == IcsExamining) {
6496       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6497         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6498         SendToICS("draw ");
6499         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6500       }
6501       // also send plain move, in case ICS does not understand atomic claims
6502       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6503       ics_user_moved = 1;
6504     }
6505   } else {
6506     if (first.sendTime && (gameMode == BeginningOfGame ||
6507                            gameMode == MachinePlaysWhite ||
6508                            gameMode == MachinePlaysBlack)) {
6509       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6510     }
6511     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6512          // [HGM] book: if program might be playing, let it use book
6513         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6514         first.maybeThinking = TRUE;
6515     } else SendMoveToProgram(forwardMostMove-1, &first);
6516     if (currentMove == cmailOldMove + 1) {
6517       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6518     }
6519   }
6520
6521   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6522
6523   switch (gameMode) {
6524   case EditGame:
6525     if(appData.testLegality)
6526     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6527     case MT_NONE:
6528     case MT_CHECK:
6529       break;
6530     case MT_CHECKMATE:
6531     case MT_STAINMATE:
6532       if (WhiteOnMove(currentMove)) {
6533         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6534       } else {
6535         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6536       }
6537       break;
6538     case MT_STALEMATE:
6539       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6540       break;
6541     }
6542     break;
6543
6544   case MachinePlaysBlack:
6545   case MachinePlaysWhite:
6546     /* disable certain menu options while machine is thinking */
6547     SetMachineThinkingEnables();
6548     break;
6549
6550   default:
6551     break;
6552   }
6553
6554   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6555   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6556
6557   if(bookHit) { // [HGM] book: simulate book reply
6558         static char bookMove[MSG_SIZ]; // a bit generous?
6559
6560         programStats.nodes = programStats.depth = programStats.time =
6561         programStats.score = programStats.got_only_move = 0;
6562         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6563
6564         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6565         strcat(bookMove, bookHit);
6566         HandleMachineMove(bookMove, &first);
6567   }
6568   return 1;
6569 }
6570
6571 void
6572 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6573      Board board;
6574      int flags;
6575      ChessMove kind;
6576      int rf, ff, rt, ft;
6577      VOIDSTAR closure;
6578 {
6579     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6580     Markers *m = (Markers *) closure;
6581     if(rf == fromY && ff == fromX)
6582         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6583                          || kind == WhiteCapturesEnPassant
6584                          || kind == BlackCapturesEnPassant);
6585     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6586 }
6587
6588 void
6589 MarkTargetSquares(int clear)
6590 {
6591   int x, y;
6592   if(!appData.markers || !appData.highlightDragging ||
6593      !appData.testLegality || gameMode == EditPosition) return;
6594   if(clear) {
6595     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6596   } else {
6597     int capt = 0;
6598     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6599     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6600       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6601       if(capt)
6602       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6603     }
6604   }
6605   DrawPosition(TRUE, NULL);
6606 }
6607
6608 int
6609 Explode(Board board, int fromX, int fromY, int toX, int toY)
6610 {
6611     if(gameInfo.variant == VariantAtomic &&
6612        (board[toY][toX] != EmptySquare ||                     // capture?
6613         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6614                          board[fromY][fromX] == BlackPawn   )
6615       )) {
6616         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6617         return TRUE;
6618     }
6619     return FALSE;
6620 }
6621
6622 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6623
6624 int CanPromote(ChessSquare piece, int y)
6625 {
6626         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6627         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6628         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6629            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6630            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6631                                                   gameInfo.variant == VariantMakruk) return FALSE;
6632         return (piece == BlackPawn && y == 1 ||
6633                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6634                 piece == BlackLance && y == 1 ||
6635                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6636 }
6637
6638 void LeftClick(ClickType clickType, int xPix, int yPix)
6639 {
6640     int x, y;
6641     Boolean saveAnimate;
6642     static int second = 0, promotionChoice = 0, clearFlag = 0;
6643     char promoChoice = NULLCHAR;
6644     ChessSquare piece;
6645
6646     if(appData.seekGraph && appData.icsActive && loggedOn &&
6647         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6648         SeekGraphClick(clickType, xPix, yPix, 0);
6649         return;
6650     }
6651
6652     if (clickType == Press) ErrorPopDown();
6653     MarkTargetSquares(1);
6654
6655     x = EventToSquare(xPix, BOARD_WIDTH);
6656     y = EventToSquare(yPix, BOARD_HEIGHT);
6657     if (!flipView && y >= 0) {
6658         y = BOARD_HEIGHT - 1 - y;
6659     }
6660     if (flipView && x >= 0) {
6661         x = BOARD_WIDTH - 1 - x;
6662     }
6663
6664     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6665         defaultPromoChoice = promoSweep;
6666         promoSweep = EmptySquare;   // terminate sweep
6667         promoDefaultAltered = TRUE;
6668         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6669     }
6670
6671     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6672         if(clickType == Release) return; // ignore upclick of click-click destination
6673         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6674         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6675         if(gameInfo.holdingsWidth &&
6676                 (WhiteOnMove(currentMove)
6677                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6678                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6679             // click in right holdings, for determining promotion piece
6680             ChessSquare p = boards[currentMove][y][x];
6681             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6682             if(p != EmptySquare) {
6683                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6684                 fromX = fromY = -1;
6685                 return;
6686             }
6687         }
6688         DrawPosition(FALSE, boards[currentMove]);
6689         return;
6690     }
6691
6692     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6693     if(clickType == Press
6694             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6695               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6696               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6697         return;
6698
6699     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6700         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6701
6702     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6703         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6704                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6705         defaultPromoChoice = DefaultPromoChoice(side);
6706     }
6707
6708     autoQueen = appData.alwaysPromoteToQueen;
6709
6710     if (fromX == -1) {
6711       int originalY = y;
6712       gatingPiece = EmptySquare;
6713       if (clickType != Press) {
6714         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6715             DragPieceEnd(xPix, yPix); dragging = 0;
6716             DrawPosition(FALSE, NULL);
6717         }
6718         return;
6719       }
6720       fromX = x; fromY = y;
6721       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6722          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6723          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6724             /* First square */
6725             if (OKToStartUserMove(fromX, fromY)) {
6726                 second = 0;
6727                 MarkTargetSquares(0);
6728                 DragPieceBegin(xPix, yPix); dragging = 1;
6729                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6730                     promoSweep = defaultPromoChoice;
6731                     selectFlag = 0; lastX = xPix; lastY = yPix;
6732                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6733                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6734                 }
6735                 if (appData.highlightDragging) {
6736                     SetHighlights(fromX, fromY, -1, -1);
6737                 }
6738             } else fromX = fromY = -1;
6739             return;
6740         }
6741     }
6742
6743     /* fromX != -1 */
6744     if (clickType == Press && gameMode != EditPosition) {
6745         ChessSquare fromP;
6746         ChessSquare toP;
6747         int frc;
6748
6749         // ignore off-board to clicks
6750         if(y < 0 || x < 0) return;
6751
6752         /* Check if clicking again on the same color piece */
6753         fromP = boards[currentMove][fromY][fromX];
6754         toP = boards[currentMove][y][x];
6755         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6756         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6757              WhitePawn <= toP && toP <= WhiteKing &&
6758              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6759              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6760             (BlackPawn <= fromP && fromP <= BlackKing &&
6761              BlackPawn <= toP && toP <= BlackKing &&
6762              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6763              !(fromP == BlackKing && toP == BlackRook && frc))) {
6764             /* Clicked again on same color piece -- changed his mind */
6765             second = (x == fromX && y == fromY);
6766             promoDefaultAltered = FALSE;
6767            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6768             if (appData.highlightDragging) {
6769                 SetHighlights(x, y, -1, -1);
6770             } else {
6771                 ClearHighlights();
6772             }
6773             if (OKToStartUserMove(x, y)) {
6774                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6775                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6776                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6777                  gatingPiece = boards[currentMove][fromY][fromX];
6778                 else gatingPiece = EmptySquare;
6779                 fromX = x;
6780                 fromY = y; dragging = 1;
6781                 MarkTargetSquares(0);
6782                 DragPieceBegin(xPix, yPix);
6783                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6784                     promoSweep = defaultPromoChoice;
6785                     selectFlag = 0; lastX = xPix; lastY = yPix;
6786                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6787                 }
6788             }
6789            }
6790            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6791            second = FALSE; 
6792         }
6793         // ignore clicks on holdings
6794         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6795     }
6796
6797     if (clickType == Release && x == fromX && y == fromY) {
6798         DragPieceEnd(xPix, yPix); dragging = 0;
6799         if(clearFlag) {
6800             // a deferred attempt to click-click move an empty square on top of a piece
6801             boards[currentMove][y][x] = EmptySquare;
6802             ClearHighlights();
6803             DrawPosition(FALSE, boards[currentMove]);
6804             fromX = fromY = -1; clearFlag = 0;
6805             return;
6806         }
6807         if (appData.animateDragging) {
6808             /* Undo animation damage if any */
6809             DrawPosition(FALSE, NULL);
6810         }
6811         if (second) {
6812             /* Second up/down in same square; just abort move */
6813             second = 0;
6814             fromX = fromY = -1;
6815             gatingPiece = EmptySquare;
6816             ClearHighlights();
6817             gotPremove = 0;
6818             ClearPremoveHighlights();
6819         } else {
6820             /* First upclick in same square; start click-click mode */
6821             SetHighlights(x, y, -1, -1);
6822         }
6823         return;
6824     }
6825
6826     clearFlag = 0;
6827
6828     /* we now have a different from- and (possibly off-board) to-square */
6829     /* Completed move */
6830     toX = x;
6831     toY = y;
6832     saveAnimate = appData.animate;
6833     if (clickType == Press) {
6834         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6835             // must be Edit Position mode with empty-square selected
6836             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6837             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6838             return;
6839         }
6840         /* Finish clickclick move */
6841         if (appData.animate || appData.highlightLastMove) {
6842             SetHighlights(fromX, fromY, toX, toY);
6843         } else {
6844             ClearHighlights();
6845         }
6846     } else {
6847         /* Finish drag move */
6848         if (appData.highlightLastMove) {
6849             SetHighlights(fromX, fromY, toX, toY);
6850         } else {
6851             ClearHighlights();
6852         }
6853         DragPieceEnd(xPix, yPix); dragging = 0;
6854         /* Don't animate move and drag both */
6855         appData.animate = FALSE;
6856     }
6857
6858     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6859     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6860         ChessSquare piece = boards[currentMove][fromY][fromX];
6861         if(gameMode == EditPosition && piece != EmptySquare &&
6862            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6863             int n;
6864
6865             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6866                 n = PieceToNumber(piece - (int)BlackPawn);
6867                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6868                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6869                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6870             } else
6871             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6872                 n = PieceToNumber(piece);
6873                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6874                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6875                 boards[currentMove][n][BOARD_WIDTH-2]++;
6876             }
6877             boards[currentMove][fromY][fromX] = EmptySquare;
6878         }
6879         ClearHighlights();
6880         fromX = fromY = -1;
6881         DrawPosition(TRUE, boards[currentMove]);
6882         return;
6883     }
6884
6885     // off-board moves should not be highlighted
6886     if(x < 0 || y < 0) ClearHighlights();
6887
6888     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6889
6890     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6891         SetHighlights(fromX, fromY, toX, toY);
6892         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6893             // [HGM] super: promotion to captured piece selected from holdings
6894             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6895             promotionChoice = TRUE;
6896             // kludge follows to temporarily execute move on display, without promoting yet
6897             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6898             boards[currentMove][toY][toX] = p;
6899             DrawPosition(FALSE, boards[currentMove]);
6900             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6901             boards[currentMove][toY][toX] = q;
6902             DisplayMessage("Click in holdings to choose piece", "");
6903             return;
6904         }
6905         PromotionPopUp();
6906     } else {
6907         int oldMove = currentMove;
6908         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6909         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6910         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6911         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6912            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6913             DrawPosition(TRUE, boards[currentMove]);
6914         fromX = fromY = -1;
6915     }
6916     appData.animate = saveAnimate;
6917     if (appData.animate || appData.animateDragging) {
6918         /* Undo animation damage if needed */
6919         DrawPosition(FALSE, NULL);
6920     }
6921 }
6922
6923 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6924 {   // front-end-free part taken out of PieceMenuPopup
6925     int whichMenu; int xSqr, ySqr;
6926
6927     if(seekGraphUp) { // [HGM] seekgraph
6928         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6929         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6930         return -2;
6931     }
6932
6933     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6934          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6935         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6936         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6937         if(action == Press)   {
6938             originalFlip = flipView;
6939             flipView = !flipView; // temporarily flip board to see game from partners perspective
6940             DrawPosition(TRUE, partnerBoard);
6941             DisplayMessage(partnerStatus, "");
6942             partnerUp = TRUE;
6943         } else if(action == Release) {
6944             flipView = originalFlip;
6945             DrawPosition(TRUE, boards[currentMove]);
6946             partnerUp = FALSE;
6947         }
6948         return -2;
6949     }
6950
6951     xSqr = EventToSquare(x, BOARD_WIDTH);
6952     ySqr = EventToSquare(y, BOARD_HEIGHT);
6953     if (action == Release) {
6954         if(pieceSweep != EmptySquare) {
6955             EditPositionMenuEvent(pieceSweep, toX, toY);
6956             pieceSweep = EmptySquare;
6957         } else UnLoadPV(); // [HGM] pv
6958     }
6959     if (action != Press) return -2; // return code to be ignored
6960     switch (gameMode) {
6961       case IcsExamining:
6962         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6963       case EditPosition:
6964         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6965         if (xSqr < 0 || ySqr < 0) return -1;
6966         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6967         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6968         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6969         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6970         NextPiece(0);
6971         return -2;\r
6972       case IcsObserving:
6973         if(!appData.icsEngineAnalyze) return -1;
6974       case IcsPlayingWhite:
6975       case IcsPlayingBlack:
6976         if(!appData.zippyPlay) goto noZip;
6977       case AnalyzeMode:
6978       case AnalyzeFile:
6979       case MachinePlaysWhite:
6980       case MachinePlaysBlack:
6981       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6982         if (!appData.dropMenu) {
6983           LoadPV(x, y);
6984           return 2; // flag front-end to grab mouse events
6985         }
6986         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6987            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6988       case EditGame:
6989       noZip:
6990         if (xSqr < 0 || ySqr < 0) return -1;
6991         if (!appData.dropMenu || appData.testLegality &&
6992             gameInfo.variant != VariantBughouse &&
6993             gameInfo.variant != VariantCrazyhouse) return -1;
6994         whichMenu = 1; // drop menu
6995         break;
6996       default:
6997         return -1;
6998     }
6999
7000     if (((*fromX = xSqr) < 0) ||
7001         ((*fromY = ySqr) < 0)) {
7002         *fromX = *fromY = -1;
7003         return -1;
7004     }
7005     if (flipView)
7006       *fromX = BOARD_WIDTH - 1 - *fromX;
7007     else
7008       *fromY = BOARD_HEIGHT - 1 - *fromY;
7009
7010     return whichMenu;
7011 }
7012
7013 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7014 {
7015 //    char * hint = lastHint;
7016     FrontEndProgramStats stats;
7017
7018     stats.which = cps == &first ? 0 : 1;
7019     stats.depth = cpstats->depth;
7020     stats.nodes = cpstats->nodes;
7021     stats.score = cpstats->score;
7022     stats.time = cpstats->time;
7023     stats.pv = cpstats->movelist;
7024     stats.hint = lastHint;
7025     stats.an_move_index = 0;
7026     stats.an_move_count = 0;
7027
7028     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7029         stats.hint = cpstats->move_name;
7030         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7031         stats.an_move_count = cpstats->nr_moves;
7032     }
7033
7034     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
7035
7036     SetProgramStats( &stats );
7037 }
7038
7039 void
7040 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7041 {       // count all piece types
7042         int p, f, r;
7043         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7044         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7045         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7046                 p = board[r][f];
7047                 pCnt[p]++;
7048                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7049                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7050                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7051                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7052                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7053                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7054         }
7055 }
7056
7057 int
7058 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7059 {
7060         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7061         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7062
7063         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7064         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7065         if(myPawns == 2 && nMine == 3) // KPP
7066             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7067         if(myPawns == 1 && nMine == 2) // KP
7068             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7069         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7070             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7071         if(myPawns) return FALSE;
7072         if(pCnt[WhiteRook+side])
7073             return pCnt[BlackRook-side] ||
7074                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7075                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7076                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7077         if(pCnt[WhiteCannon+side]) {
7078             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7079             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7080         }
7081         if(pCnt[WhiteKnight+side])
7082             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7083         return FALSE;
7084 }
7085
7086 int
7087 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7088 {
7089         VariantClass v = gameInfo.variant;
7090
7091         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7092         if(v == VariantShatranj) return TRUE; // always winnable through baring
7093         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7094         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7095
7096         if(v == VariantXiangqi) {
7097                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7098
7099                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7100                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7101                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7102                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7103                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7104                 if(stale) // we have at least one last-rank P plus perhaps C
7105                     return majors // KPKX
7106                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7107                 else // KCA*E*
7108                     return pCnt[WhiteFerz+side] // KCAK
7109                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7110                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7111                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7112
7113         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7114                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7115
7116                 if(nMine == 1) return FALSE; // bare King
7117                 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
7118                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7119                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7120                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7121                 if(pCnt[WhiteKnight+side])
7122                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7123                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7124                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7125                 if(nBishops)
7126                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7127                 if(pCnt[WhiteAlfil+side])
7128                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7129                 if(pCnt[WhiteWazir+side])
7130                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7131         }
7132
7133         return TRUE;
7134 }
7135
7136 int
7137 Adjudicate(ChessProgramState *cps)
7138 {       // [HGM] some adjudications useful with buggy engines
7139         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7140         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7141         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7142         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7143         int k, count = 0; static int bare = 1;
7144         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7145         Boolean canAdjudicate = !appData.icsActive;
7146
7147         // most tests only when we understand the game, i.e. legality-checking on
7148             if( appData.testLegality )
7149             {   /* [HGM] Some more adjudications for obstinate engines */
7150                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7151                 static int moveCount = 6;
7152                 ChessMove result;
7153                 char *reason = NULL;
7154
7155                 /* Count what is on board. */
7156                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7157
7158                 /* Some material-based adjudications that have to be made before stalemate test */
7159                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7160                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7161                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7162                      if(canAdjudicate && appData.checkMates) {
7163                          if(engineOpponent)
7164                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7165                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7166                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7167                          return 1;
7168                      }
7169                 }
7170
7171                 /* Bare King in Shatranj (loses) or Losers (wins) */
7172                 if( nrW == 1 || nrB == 1) {
7173                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7174                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7175                      if(canAdjudicate && appData.checkMates) {
7176                          if(engineOpponent)
7177                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7178                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7179                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7180                          return 1;
7181                      }
7182                   } else
7183                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7184                   {    /* bare King */
7185                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7186                         if(canAdjudicate && appData.checkMates) {
7187                             /* but only adjudicate if adjudication enabled */
7188                             if(engineOpponent)
7189                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7190                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7191                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7192                             return 1;
7193                         }
7194                   }
7195                 } else bare = 1;
7196
7197
7198             // don't wait for engine to announce game end if we can judge ourselves
7199             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7200               case MT_CHECK:
7201                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7202                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7203                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7204                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7205                             checkCnt++;
7206                         if(checkCnt >= 2) {
7207                             reason = "Xboard adjudication: 3rd check";
7208                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7209                             break;
7210                         }
7211                     }
7212                 }
7213               case MT_NONE:
7214               default:
7215                 break;
7216               case MT_STALEMATE:
7217               case MT_STAINMATE:
7218                 reason = "Xboard adjudication: Stalemate";
7219                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7220                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7221                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7222                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7223                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7224                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7225                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7226                                                                         EP_CHECKMATE : EP_WINS);
7227                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7228                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7229                 }
7230                 break;
7231               case MT_CHECKMATE:
7232                 reason = "Xboard adjudication: Checkmate";
7233                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7234                 break;
7235             }
7236
7237                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7238                     case EP_STALEMATE:
7239                         result = GameIsDrawn; break;
7240                     case EP_CHECKMATE:
7241                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7242                     case EP_WINS:
7243                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7244                     default:
7245                         result = EndOfFile;
7246                 }
7247                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7248                     if(engineOpponent)
7249                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7250                     GameEnds( result, reason, GE_XBOARD );
7251                     return 1;
7252                 }
7253
7254                 /* Next absolutely insufficient mating material. */
7255                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7256                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7257                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7258
7259                      /* always flag draws, for judging claims */
7260                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7261
7262                      if(canAdjudicate && appData.materialDraws) {
7263                          /* but only adjudicate them if adjudication enabled */
7264                          if(engineOpponent) {
7265                            SendToProgram("force\n", engineOpponent); // suppress reply
7266                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7267                          }
7268                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7269                          return 1;
7270                      }
7271                 }
7272
7273                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7274                 if(gameInfo.variant == VariantXiangqi ?
7275                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7276                  : nrW + nrB == 4 &&
7277                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7278                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7279                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7280                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7281                    ) ) {
7282                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7283                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7284                           if(engineOpponent) {
7285                             SendToProgram("force\n", engineOpponent); // suppress reply
7286                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7287                           }
7288                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7289                           return 1;
7290                      }
7291                 } else moveCount = 6;
7292             }
7293         if (appData.debugMode) { int i;
7294             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7295                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7296                     appData.drawRepeats);
7297             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7298               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7299
7300         }
7301
7302         // Repetition draws and 50-move rule can be applied independently of legality testing
7303
7304                 /* Check for rep-draws */
7305                 count = 0;
7306                 for(k = forwardMostMove-2;
7307                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7308                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7309                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7310                     k-=2)
7311                 {   int rights=0;
7312                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7313                         /* compare castling rights */
7314                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7315                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7316                                 rights++; /* King lost rights, while rook still had them */
7317                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7318                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7319                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7320                                    rights++; /* but at least one rook lost them */
7321                         }
7322                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7323                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7324                                 rights++;
7325                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7326                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7327                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7328                                    rights++;
7329                         }
7330                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7331                             && appData.drawRepeats > 1) {
7332                              /* adjudicate after user-specified nr of repeats */
7333                              int result = GameIsDrawn;
7334                              char *details = "XBoard adjudication: repetition draw";
7335                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7336                                 // [HGM] xiangqi: check for forbidden perpetuals
7337                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7338                                 for(m=forwardMostMove; m>k; m-=2) {
7339                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7340                                         ourPerpetual = 0; // the current mover did not always check
7341                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7342                                         hisPerpetual = 0; // the opponent did not always check
7343                                 }
7344                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7345                                                                         ourPerpetual, hisPerpetual);
7346                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7347                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7348                                     details = "Xboard adjudication: perpetual checking";
7349                                 } else
7350                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7351                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7352                                 } else
7353                                 // Now check for perpetual chases
7354                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7355                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7356                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7357                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7358                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7359                                         details = "Xboard adjudication: perpetual chasing";
7360                                     } else
7361                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7362                                         break; // Abort repetition-checking loop.
7363                                 }
7364                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7365                              }
7366                              if(engineOpponent) {
7367                                SendToProgram("force\n", engineOpponent); // suppress reply
7368                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7369                              }
7370                              GameEnds( result, details, GE_XBOARD );
7371                              return 1;
7372                         }
7373                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7374                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7375                     }
7376                 }
7377
7378                 /* Now we test for 50-move draws. Determine ply count */
7379                 count = forwardMostMove;
7380                 /* look for last irreversble move */
7381                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7382                     count--;
7383                 /* if we hit starting position, add initial plies */
7384                 if( count == backwardMostMove )
7385                     count -= initialRulePlies;
7386                 count = forwardMostMove - count;
7387                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7388                         // adjust reversible move counter for checks in Xiangqi
7389                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7390                         if(i < backwardMostMove) i = backwardMostMove;
7391                         while(i <= forwardMostMove) {
7392                                 lastCheck = inCheck; // check evasion does not count
7393                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7394                                 if(inCheck || lastCheck) count--; // check does not count
7395                                 i++;
7396                         }
7397                 }
7398                 if( count >= 100)
7399                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7400                          /* this is used to judge if draw claims are legal */
7401                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7402                          if(engineOpponent) {
7403                            SendToProgram("force\n", engineOpponent); // suppress reply
7404                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7405                          }
7406                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7407                          return 1;
7408                 }
7409
7410                 /* if draw offer is pending, treat it as a draw claim
7411                  * when draw condition present, to allow engines a way to
7412                  * claim draws before making their move to avoid a race
7413                  * condition occurring after their move
7414                  */
7415                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7416                          char *p = NULL;
7417                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7418                              p = "Draw claim: 50-move rule";
7419                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7420                              p = "Draw claim: 3-fold repetition";
7421                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7422                              p = "Draw claim: insufficient mating material";
7423                          if( p != NULL && canAdjudicate) {
7424                              if(engineOpponent) {
7425                                SendToProgram("force\n", engineOpponent); // suppress reply
7426                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7427                              }
7428                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7429                              return 1;
7430                          }
7431                 }
7432
7433                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7434                     if(engineOpponent) {
7435                       SendToProgram("force\n", engineOpponent); // suppress reply
7436                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7437                     }
7438                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7439                     return 1;
7440                 }
7441         return 0;
7442 }
7443
7444 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7445 {   // [HGM] book: this routine intercepts moves to simulate book replies
7446     char *bookHit = NULL;
7447
7448     //first determine if the incoming move brings opponent into his book
7449     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7450         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7451     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7452     if(bookHit != NULL && !cps->bookSuspend) {
7453         // make sure opponent is not going to reply after receiving move to book position
7454         SendToProgram("force\n", cps);
7455         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7456     }
7457     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7458     // now arrange restart after book miss
7459     if(bookHit) {
7460         // after a book hit we never send 'go', and the code after the call to this routine
7461         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7462         char buf[MSG_SIZ];
7463         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7464         SendToProgram(buf, cps);
7465         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7466     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7467         SendToProgram("go\n", cps);
7468         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7469     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7470         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7471             SendToProgram("go\n", cps);
7472         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7473     }
7474     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7475 }
7476
7477 char *savedMessage;
7478 ChessProgramState *savedState;
7479 void DeferredBookMove(void)
7480 {
7481         if(savedState->lastPing != savedState->lastPong)
7482                     ScheduleDelayedEvent(DeferredBookMove, 10);
7483         else
7484         HandleMachineMove(savedMessage, savedState);
7485 }
7486
7487 void
7488 HandleMachineMove(message, cps)
7489      char *message;
7490      ChessProgramState *cps;
7491 {
7492     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7493     char realname[MSG_SIZ];
7494     int fromX, fromY, toX, toY;
7495     ChessMove moveType;
7496     char promoChar;
7497     char *p;
7498     int machineWhite;
7499     char *bookHit;
7500
7501     cps->userError = 0;
7502
7503 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7504     /*
7505      * Kludge to ignore BEL characters
7506      */
7507     while (*message == '\007') message++;
7508
7509     /*
7510      * [HGM] engine debug message: ignore lines starting with '#' character
7511      */
7512     if(cps->debug && *message == '#') return;
7513
7514     /*
7515      * Look for book output
7516      */
7517     if (cps == &first && bookRequested) {
7518         if (message[0] == '\t' || message[0] == ' ') {
7519             /* Part of the book output is here; append it */
7520             strcat(bookOutput, message);
7521             strcat(bookOutput, "  \n");
7522             return;
7523         } else if (bookOutput[0] != NULLCHAR) {
7524             /* All of book output has arrived; display it */
7525             char *p = bookOutput;
7526             while (*p != NULLCHAR) {
7527                 if (*p == '\t') *p = ' ';
7528                 p++;
7529             }
7530             DisplayInformation(bookOutput);
7531             bookRequested = FALSE;
7532             /* Fall through to parse the current output */
7533         }
7534     }
7535
7536     /*
7537      * Look for machine move.
7538      */
7539     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7540         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7541     {
7542         /* This method is only useful on engines that support ping */
7543         if (cps->lastPing != cps->lastPong) {
7544           if (gameMode == BeginningOfGame) {
7545             /* Extra move from before last new; ignore */
7546             if (appData.debugMode) {
7547                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7548             }
7549           } else {
7550             if (appData.debugMode) {
7551                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7552                         cps->which, gameMode);
7553             }
7554
7555             SendToProgram("undo\n", cps);
7556           }
7557           return;
7558         }
7559
7560         switch (gameMode) {
7561           case BeginningOfGame:
7562             /* Extra move from before last reset; ignore */
7563             if (appData.debugMode) {
7564                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7565             }
7566             return;
7567
7568           case EndOfGame:
7569           case IcsIdle:
7570           default:
7571             /* Extra move after we tried to stop.  The mode test is
7572                not a reliable way of detecting this problem, but it's
7573                the best we can do on engines that don't support ping.
7574             */
7575             if (appData.debugMode) {
7576                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7577                         cps->which, gameMode);
7578             }
7579             SendToProgram("undo\n", cps);
7580             return;
7581
7582           case MachinePlaysWhite:
7583           case IcsPlayingWhite:
7584             machineWhite = TRUE;
7585             break;
7586
7587           case MachinePlaysBlack:
7588           case IcsPlayingBlack:
7589             machineWhite = FALSE;
7590             break;
7591
7592           case TwoMachinesPlay:
7593             machineWhite = (cps->twoMachinesColor[0] == 'w');
7594             break;
7595         }
7596         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7597             if (appData.debugMode) {
7598                 fprintf(debugFP,
7599                         "Ignoring move out of turn by %s, gameMode %d"
7600                         ", forwardMost %d\n",
7601                         cps->which, gameMode, forwardMostMove);
7602             }
7603             return;
7604         }
7605
7606     if (appData.debugMode) { int f = forwardMostMove;
7607         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7608                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7609                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7610     }
7611         if(cps->alphaRank) AlphaRank(machineMove, 4);
7612         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7613                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7614             /* Machine move could not be parsed; ignore it. */
7615           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7616                     machineMove, _(cps->which));
7617             DisplayError(buf1, 0);
7618             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7619                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7620             if (gameMode == TwoMachinesPlay) {
7621               GameEnds(machineWhite ? BlackWins : WhiteWins,
7622                        buf1, GE_XBOARD);
7623             }
7624             return;
7625         }
7626
7627         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7628         /* So we have to redo legality test with true e.p. status here,  */
7629         /* to make sure an illegal e.p. capture does not slip through,   */
7630         /* to cause a forfeit on a justified illegal-move complaint      */
7631         /* of the opponent.                                              */
7632         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7633            ChessMove moveType;
7634            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7635                              fromY, fromX, toY, toX, promoChar);
7636             if (appData.debugMode) {
7637                 int i;
7638                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7639                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7640                 fprintf(debugFP, "castling rights\n");
7641             }
7642             if(moveType == IllegalMove) {
7643               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7644                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7645                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7646                            buf1, GE_XBOARD);
7647                 return;
7648            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7649            /* [HGM] Kludge to handle engines that send FRC-style castling
7650               when they shouldn't (like TSCP-Gothic) */
7651            switch(moveType) {
7652              case WhiteASideCastleFR:
7653              case BlackASideCastleFR:
7654                toX+=2;
7655                currentMoveString[2]++;
7656                break;
7657              case WhiteHSideCastleFR:
7658              case BlackHSideCastleFR:
7659                toX--;
7660                currentMoveString[2]--;
7661                break;
7662              default: ; // nothing to do, but suppresses warning of pedantic compilers
7663            }
7664         }
7665         hintRequested = FALSE;
7666         lastHint[0] = NULLCHAR;
7667         bookRequested = FALSE;
7668         /* Program may be pondering now */
7669         cps->maybeThinking = TRUE;
7670         if (cps->sendTime == 2) cps->sendTime = 1;
7671         if (cps->offeredDraw) cps->offeredDraw--;
7672
7673         /* [AS] Save move info*/
7674         pvInfoList[ forwardMostMove ].score = programStats.score;
7675         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7676         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7677
7678         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7679
7680         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7681         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7682             int count = 0;
7683
7684             while( count < adjudicateLossPlies ) {
7685                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7686
7687                 if( count & 1 ) {
7688                     score = -score; /* Flip score for winning side */
7689                 }
7690
7691                 if( score > adjudicateLossThreshold ) {
7692                     break;
7693                 }
7694
7695                 count++;
7696             }
7697
7698             if( count >= adjudicateLossPlies ) {
7699                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7700
7701                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7702                     "Xboard adjudication",
7703                     GE_XBOARD );
7704
7705                 return;
7706             }
7707         }
7708
7709         if(Adjudicate(cps)) {
7710             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7711             return; // [HGM] adjudicate: for all automatic game ends
7712         }
7713
7714 #if ZIPPY
7715         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7716             first.initDone) {
7717           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7718                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7719                 SendToICS("draw ");
7720                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7721           }
7722           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7723           ics_user_moved = 1;
7724           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7725                 char buf[3*MSG_SIZ];
7726
7727                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7728                         programStats.score / 100.,
7729                         programStats.depth,
7730                         programStats.time / 100.,
7731                         (unsigned int)programStats.nodes,
7732                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7733                         programStats.movelist);
7734                 SendToICS(buf);
7735 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7736           }
7737         }
7738 #endif
7739
7740         /* [AS] Clear stats for next move */
7741         ClearProgramStats();
7742         thinkOutput[0] = NULLCHAR;
7743         hiddenThinkOutputState = 0;
7744
7745         bookHit = NULL;
7746         if (gameMode == TwoMachinesPlay) {
7747             /* [HGM] relaying draw offers moved to after reception of move */
7748             /* and interpreting offer as claim if it brings draw condition */
7749             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7750                 SendToProgram("draw\n", cps->other);
7751             }
7752             if (cps->other->sendTime) {
7753                 SendTimeRemaining(cps->other,
7754                                   cps->other->twoMachinesColor[0] == 'w');
7755             }
7756             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7757             if (firstMove && !bookHit) {
7758                 firstMove = FALSE;
7759                 if (cps->other->useColors) {
7760                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7761                 }
7762                 SendToProgram("go\n", cps->other);
7763             }
7764             cps->other->maybeThinking = TRUE;
7765         }
7766
7767         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7768
7769         if (!pausing && appData.ringBellAfterMoves) {
7770             RingBell();
7771         }
7772
7773         /*
7774          * Reenable menu items that were disabled while
7775          * machine was thinking
7776          */
7777         if (gameMode != TwoMachinesPlay)
7778             SetUserThinkingEnables();
7779
7780         // [HGM] book: after book hit opponent has received move and is now in force mode
7781         // force the book reply into it, and then fake that it outputted this move by jumping
7782         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7783         if(bookHit) {
7784                 static char bookMove[MSG_SIZ]; // a bit generous?
7785
7786                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7787                 strcat(bookMove, bookHit);
7788                 message = bookMove;
7789                 cps = cps->other;
7790                 programStats.nodes = programStats.depth = programStats.time =
7791                 programStats.score = programStats.got_only_move = 0;
7792                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7793
7794                 if(cps->lastPing != cps->lastPong) {
7795                     savedMessage = message; // args for deferred call
7796                     savedState = cps;
7797                     ScheduleDelayedEvent(DeferredBookMove, 10);
7798                     return;
7799                 }
7800                 goto FakeBookMove;
7801         }
7802
7803         return;
7804     }
7805
7806     /* Set special modes for chess engines.  Later something general
7807      *  could be added here; for now there is just one kludge feature,
7808      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7809      *  when "xboard" is given as an interactive command.
7810      */
7811     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7812         cps->useSigint = FALSE;
7813         cps->useSigterm = FALSE;
7814     }
7815     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7816       ParseFeatures(message+8, cps);
7817       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7818     }
7819
7820     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7821       int dummy, s=6; char buf[MSG_SIZ];
7822       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7823       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7824       ParseFEN(boards[0], &dummy, message+s);
7825       DrawPosition(TRUE, boards[0]);
7826       startedFromSetupPosition = TRUE;
7827       return;
7828     }
7829     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7830      * want this, I was asked to put it in, and obliged.
7831      */
7832     if (!strncmp(message, "setboard ", 9)) {
7833         Board initial_position;
7834
7835         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7836
7837         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7838             DisplayError(_("Bad FEN received from engine"), 0);
7839             return ;
7840         } else {
7841            Reset(TRUE, FALSE);
7842            CopyBoard(boards[0], initial_position);
7843            initialRulePlies = FENrulePlies;
7844            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7845            else gameMode = MachinePlaysBlack;
7846            DrawPosition(FALSE, boards[currentMove]);
7847         }
7848         return;
7849     }
7850
7851     /*
7852      * Look for communication commands
7853      */
7854     if (!strncmp(message, "telluser ", 9)) {
7855         if(message[9] == '\\' && message[10] == '\\')
7856             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7857         DisplayNote(message + 9);
7858         return;
7859     }
7860     if (!strncmp(message, "tellusererror ", 14)) {
7861         cps->userError = 1;
7862         if(message[14] == '\\' && message[15] == '\\')
7863             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7864         DisplayError(message + 14, 0);
7865         return;
7866     }
7867     if (!strncmp(message, "tellopponent ", 13)) {
7868       if (appData.icsActive) {
7869         if (loggedOn) {
7870           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7871           SendToICS(buf1);
7872         }
7873       } else {
7874         DisplayNote(message + 13);
7875       }
7876       return;
7877     }
7878     if (!strncmp(message, "tellothers ", 11)) {
7879       if (appData.icsActive) {
7880         if (loggedOn) {
7881           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7882           SendToICS(buf1);
7883         }
7884       }
7885       return;
7886     }
7887     if (!strncmp(message, "tellall ", 8)) {
7888       if (appData.icsActive) {
7889         if (loggedOn) {
7890           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7891           SendToICS(buf1);
7892         }
7893       } else {
7894         DisplayNote(message + 8);
7895       }
7896       return;
7897     }
7898     if (strncmp(message, "warning", 7) == 0) {
7899         /* Undocumented feature, use tellusererror in new code */
7900         DisplayError(message, 0);
7901         return;
7902     }
7903     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7904         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7905         strcat(realname, " query");
7906         AskQuestion(realname, buf2, buf1, cps->pr);
7907         return;
7908     }
7909     /* Commands from the engine directly to ICS.  We don't allow these to be
7910      *  sent until we are logged on. Crafty kibitzes have been known to
7911      *  interfere with the login process.
7912      */
7913     if (loggedOn) {
7914         if (!strncmp(message, "tellics ", 8)) {
7915             SendToICS(message + 8);
7916             SendToICS("\n");
7917             return;
7918         }
7919         if (!strncmp(message, "tellicsnoalias ", 15)) {
7920             SendToICS(ics_prefix);
7921             SendToICS(message + 15);
7922             SendToICS("\n");
7923             return;
7924         }
7925         /* The following are for backward compatibility only */
7926         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7927             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7928             SendToICS(ics_prefix);
7929             SendToICS(message);
7930             SendToICS("\n");
7931             return;
7932         }
7933     }
7934     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7935         return;
7936     }
7937     /*
7938      * If the move is illegal, cancel it and redraw the board.
7939      * Also deal with other error cases.  Matching is rather loose
7940      * here to accommodate engines written before the spec.
7941      */
7942     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7943         strncmp(message, "Error", 5) == 0) {
7944         if (StrStr(message, "name") ||
7945             StrStr(message, "rating") || StrStr(message, "?") ||
7946             StrStr(message, "result") || StrStr(message, "board") ||
7947             StrStr(message, "bk") || StrStr(message, "computer") ||
7948             StrStr(message, "variant") || StrStr(message, "hint") ||
7949             StrStr(message, "random") || StrStr(message, "depth") ||
7950             StrStr(message, "accepted")) {
7951             return;
7952         }
7953         if (StrStr(message, "protover")) {
7954           /* Program is responding to input, so it's apparently done
7955              initializing, and this error message indicates it is
7956              protocol version 1.  So we don't need to wait any longer
7957              for it to initialize and send feature commands. */
7958           FeatureDone(cps, 1);
7959           cps->protocolVersion = 1;
7960           return;
7961         }
7962         cps->maybeThinking = FALSE;
7963
7964         if (StrStr(message, "draw")) {
7965             /* Program doesn't have "draw" command */
7966             cps->sendDrawOffers = 0;
7967             return;
7968         }
7969         if (cps->sendTime != 1 &&
7970             (StrStr(message, "time") || StrStr(message, "otim"))) {
7971           /* Program apparently doesn't have "time" or "otim" command */
7972           cps->sendTime = 0;
7973           return;
7974         }
7975         if (StrStr(message, "analyze")) {
7976             cps->analysisSupport = FALSE;
7977             cps->analyzing = FALSE;
7978             Reset(FALSE, TRUE);
7979             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7980             DisplayError(buf2, 0);
7981             return;
7982         }
7983         if (StrStr(message, "(no matching move)st")) {
7984           /* Special kludge for GNU Chess 4 only */
7985           cps->stKludge = TRUE;
7986           SendTimeControl(cps, movesPerSession, timeControl,
7987                           timeIncrement, appData.searchDepth,
7988                           searchTime);
7989           return;
7990         }
7991         if (StrStr(message, "(no matching move)sd")) {
7992           /* Special kludge for GNU Chess 4 only */
7993           cps->sdKludge = TRUE;
7994           SendTimeControl(cps, movesPerSession, timeControl,
7995                           timeIncrement, appData.searchDepth,
7996                           searchTime);
7997           return;
7998         }
7999         if (!StrStr(message, "llegal")) {
8000             return;
8001         }
8002         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8003             gameMode == IcsIdle) return;
8004         if (forwardMostMove <= backwardMostMove) return;
8005         if (pausing) PauseEvent();
8006       if(appData.forceIllegal) {
8007             // [HGM] illegal: machine refused move; force position after move into it
8008           SendToProgram("force\n", cps);
8009           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8010                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8011                 // when black is to move, while there might be nothing on a2 or black
8012                 // might already have the move. So send the board as if white has the move.
8013                 // But first we must change the stm of the engine, as it refused the last move
8014                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8015                 if(WhiteOnMove(forwardMostMove)) {
8016                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8017                     SendBoard(cps, forwardMostMove); // kludgeless board
8018                 } else {
8019                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8020                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8021                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8022                 }
8023           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8024             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8025                  gameMode == TwoMachinesPlay)
8026               SendToProgram("go\n", cps);
8027             return;
8028       } else
8029         if (gameMode == PlayFromGameFile) {
8030             /* Stop reading this game file */
8031             gameMode = EditGame;
8032             ModeHighlight();
8033         }
8034         /* [HGM] illegal-move claim should forfeit game when Xboard */
8035         /* only passes fully legal moves                            */
8036         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8037             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8038                                 "False illegal-move claim", GE_XBOARD );
8039             return; // do not take back move we tested as valid
8040         }
8041         currentMove = forwardMostMove-1;
8042         DisplayMove(currentMove-1); /* before DisplayMoveError */
8043         SwitchClocks(forwardMostMove-1); // [HGM] race
8044         DisplayBothClocks();
8045         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8046                 parseList[currentMove], _(cps->which));
8047         DisplayMoveError(buf1);
8048         DrawPosition(FALSE, boards[currentMove]);
8049         return;
8050     }
8051     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8052         /* Program has a broken "time" command that
8053            outputs a string not ending in newline.
8054            Don't use it. */
8055         cps->sendTime = 0;
8056     }
8057
8058     /*
8059      * If chess program startup fails, exit with an error message.
8060      * Attempts to recover here are futile.
8061      */
8062     if ((StrStr(message, "unknown host") != NULL)
8063         || (StrStr(message, "No remote directory") != NULL)
8064         || (StrStr(message, "not found") != NULL)
8065         || (StrStr(message, "No such file") != NULL)
8066         || (StrStr(message, "can't alloc") != NULL)
8067         || (StrStr(message, "Permission denied") != NULL)) {
8068
8069         cps->maybeThinking = FALSE;
8070         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8071                 _(cps->which), cps->program, cps->host, message);
8072         RemoveInputSource(cps->isr);
8073         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8074             if(cps == &first) appData.noChessProgram = TRUE;
8075             DisplayError(buf1, 0);
8076         }
8077         return;
8078     }
8079
8080     /*
8081      * Look for hint output
8082      */
8083     if (sscanf(message, "Hint: %s", buf1) == 1) {
8084         if (cps == &first && hintRequested) {
8085             hintRequested = FALSE;
8086             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8087                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8088                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8089                                     PosFlags(forwardMostMove),
8090                                     fromY, fromX, toY, toX, promoChar, buf1);
8091                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8092                 DisplayInformation(buf2);
8093             } else {
8094                 /* Hint move could not be parsed!? */
8095               snprintf(buf2, sizeof(buf2),
8096                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8097                         buf1, _(cps->which));
8098                 DisplayError(buf2, 0);
8099             }
8100         } else {
8101           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8102         }
8103         return;
8104     }
8105
8106     /*
8107      * Ignore other messages if game is not in progress
8108      */
8109     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8110         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8111
8112     /*
8113      * look for win, lose, draw, or draw offer
8114      */
8115     if (strncmp(message, "1-0", 3) == 0) {
8116         char *p, *q, *r = "";
8117         p = strchr(message, '{');
8118         if (p) {
8119             q = strchr(p, '}');
8120             if (q) {
8121                 *q = NULLCHAR;
8122                 r = p + 1;
8123             }
8124         }
8125         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8126         return;
8127     } else if (strncmp(message, "0-1", 3) == 0) {
8128         char *p, *q, *r = "";
8129         p = strchr(message, '{');
8130         if (p) {
8131             q = strchr(p, '}');
8132             if (q) {
8133                 *q = NULLCHAR;
8134                 r = p + 1;
8135             }
8136         }
8137         /* Kludge for Arasan 4.1 bug */
8138         if (strcmp(r, "Black resigns") == 0) {
8139             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8140             return;
8141         }
8142         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8143         return;
8144     } else if (strncmp(message, "1/2", 3) == 0) {
8145         char *p, *q, *r = "";
8146         p = strchr(message, '{');
8147         if (p) {
8148             q = strchr(p, '}');
8149             if (q) {
8150                 *q = NULLCHAR;
8151                 r = p + 1;
8152             }
8153         }
8154
8155         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8156         return;
8157
8158     } else if (strncmp(message, "White resign", 12) == 0) {
8159         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8160         return;
8161     } else if (strncmp(message, "Black resign", 12) == 0) {
8162         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8163         return;
8164     } else if (strncmp(message, "White matches", 13) == 0 ||
8165                strncmp(message, "Black matches", 13) == 0   ) {
8166         /* [HGM] ignore GNUShogi noises */
8167         return;
8168     } else if (strncmp(message, "White", 5) == 0 &&
8169                message[5] != '(' &&
8170                StrStr(message, "Black") == NULL) {
8171         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8172         return;
8173     } else if (strncmp(message, "Black", 5) == 0 &&
8174                message[5] != '(') {
8175         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8176         return;
8177     } else if (strcmp(message, "resign") == 0 ||
8178                strcmp(message, "computer resigns") == 0) {
8179         switch (gameMode) {
8180           case MachinePlaysBlack:
8181           case IcsPlayingBlack:
8182             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8183             break;
8184           case MachinePlaysWhite:
8185           case IcsPlayingWhite:
8186             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8187             break;
8188           case TwoMachinesPlay:
8189             if (cps->twoMachinesColor[0] == 'w')
8190               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8191             else
8192               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8193             break;
8194           default:
8195             /* can't happen */
8196             break;
8197         }
8198         return;
8199     } else if (strncmp(message, "opponent mates", 14) == 0) {
8200         switch (gameMode) {
8201           case MachinePlaysBlack:
8202           case IcsPlayingBlack:
8203             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8204             break;
8205           case MachinePlaysWhite:
8206           case IcsPlayingWhite:
8207             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8208             break;
8209           case TwoMachinesPlay:
8210             if (cps->twoMachinesColor[0] == 'w')
8211               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8212             else
8213               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8214             break;
8215           default:
8216             /* can't happen */
8217             break;
8218         }
8219         return;
8220     } else if (strncmp(message, "computer mates", 14) == 0) {
8221         switch (gameMode) {
8222           case MachinePlaysBlack:
8223           case IcsPlayingBlack:
8224             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8225             break;
8226           case MachinePlaysWhite:
8227           case IcsPlayingWhite:
8228             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8229             break;
8230           case TwoMachinesPlay:
8231             if (cps->twoMachinesColor[0] == 'w')
8232               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8233             else
8234               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8235             break;
8236           default:
8237             /* can't happen */
8238             break;
8239         }
8240         return;
8241     } else if (strncmp(message, "checkmate", 9) == 0) {
8242         if (WhiteOnMove(forwardMostMove)) {
8243             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8244         } else {
8245             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8246         }
8247         return;
8248     } else if (strstr(message, "Draw") != NULL ||
8249                strstr(message, "game is a draw") != NULL) {
8250         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8251         return;
8252     } else if (strstr(message, "offer") != NULL &&
8253                strstr(message, "draw") != NULL) {
8254 #if ZIPPY
8255         if (appData.zippyPlay && first.initDone) {
8256             /* Relay offer to ICS */
8257             SendToICS(ics_prefix);
8258             SendToICS("draw\n");
8259         }
8260 #endif
8261         cps->offeredDraw = 2; /* valid until this engine moves twice */
8262         if (gameMode == TwoMachinesPlay) {
8263             if (cps->other->offeredDraw) {
8264                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8265             /* [HGM] in two-machine mode we delay relaying draw offer      */
8266             /* until after we also have move, to see if it is really claim */
8267             }
8268         } else if (gameMode == MachinePlaysWhite ||
8269                    gameMode == MachinePlaysBlack) {
8270           if (userOfferedDraw) {
8271             DisplayInformation(_("Machine accepts your draw offer"));
8272             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8273           } else {
8274             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8275           }
8276         }
8277     }
8278
8279
8280     /*
8281      * Look for thinking output
8282      */
8283     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8284           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8285                                 ) {
8286         int plylev, mvleft, mvtot, curscore, time;
8287         char mvname[MOVE_LEN];
8288         u64 nodes; // [DM]
8289         char plyext;
8290         int ignore = FALSE;
8291         int prefixHint = FALSE;
8292         mvname[0] = NULLCHAR;
8293
8294         switch (gameMode) {
8295           case MachinePlaysBlack:
8296           case IcsPlayingBlack:
8297             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8298             break;
8299           case MachinePlaysWhite:
8300           case IcsPlayingWhite:
8301             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8302             break;
8303           case AnalyzeMode:
8304           case AnalyzeFile:
8305             break;
8306           case IcsObserving: /* [DM] icsEngineAnalyze */
8307             if (!appData.icsEngineAnalyze) ignore = TRUE;
8308             break;
8309           case TwoMachinesPlay:
8310             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8311                 ignore = TRUE;
8312             }
8313             break;
8314           default:
8315             ignore = TRUE;
8316             break;
8317         }
8318
8319         if (!ignore) {
8320             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8321             buf1[0] = NULLCHAR;
8322             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8323                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8324
8325                 if (plyext != ' ' && plyext != '\t') {
8326                     time *= 100;
8327                 }
8328
8329                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8330                 if( cps->scoreIsAbsolute &&
8331                     ( gameMode == MachinePlaysBlack ||
8332                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8333                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8334                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8335                      !WhiteOnMove(currentMove)
8336                     ) )
8337                 {
8338                     curscore = -curscore;
8339                 }
8340
8341
8342                 tempStats.depth = plylev;
8343                 tempStats.nodes = nodes;
8344                 tempStats.time = time;
8345                 tempStats.score = curscore;
8346                 tempStats.got_only_move = 0;
8347
8348                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8349                         int ticklen;
8350
8351                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8352                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8353                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8354                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8355                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8356                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8357                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8358                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8359                 }
8360
8361                 /* Buffer overflow protection */
8362                 if (buf1[0] != NULLCHAR) {
8363                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8364                         && appData.debugMode) {
8365                         fprintf(debugFP,
8366                                 "PV is too long; using the first %u bytes.\n",
8367                                 (unsigned) sizeof(tempStats.movelist) - 1);
8368                     }
8369
8370                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8371                 } else {
8372                     sprintf(tempStats.movelist, " no PV\n");
8373                 }
8374
8375                 if (tempStats.seen_stat) {
8376                     tempStats.ok_to_send = 1;
8377                 }
8378
8379                 if (strchr(tempStats.movelist, '(') != NULL) {
8380                     tempStats.line_is_book = 1;
8381                     tempStats.nr_moves = 0;
8382                     tempStats.moves_left = 0;
8383                 } else {
8384                     tempStats.line_is_book = 0;
8385                 }
8386
8387                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8388                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8389
8390                 SendProgramStatsToFrontend( cps, &tempStats );
8391
8392                 /*
8393                     [AS] Protect the thinkOutput buffer from overflow... this
8394                     is only useful if buf1 hasn't overflowed first!
8395                 */
8396                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8397                          plylev,
8398                          (gameMode == TwoMachinesPlay ?
8399                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8400                          ((double) curscore) / 100.0,
8401                          prefixHint ? lastHint : "",
8402                          prefixHint ? " " : "" );
8403
8404                 if( buf1[0] != NULLCHAR ) {
8405                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8406
8407                     if( strlen(buf1) > max_len ) {
8408                         if( appData.debugMode) {
8409                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8410                         }
8411                         buf1[max_len+1] = '\0';
8412                     }
8413
8414                     strcat( thinkOutput, buf1 );
8415                 }
8416
8417                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8418                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8419                     DisplayMove(currentMove - 1);
8420                 }
8421                 return;
8422
8423             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8424                 /* crafty (9.25+) says "(only move) <move>"
8425                  * if there is only 1 legal move
8426                  */
8427                 sscanf(p, "(only move) %s", buf1);
8428                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8429                 sprintf(programStats.movelist, "%s (only move)", buf1);
8430                 programStats.depth = 1;
8431                 programStats.nr_moves = 1;
8432                 programStats.moves_left = 1;
8433                 programStats.nodes = 1;
8434                 programStats.time = 1;
8435                 programStats.got_only_move = 1;
8436
8437                 /* Not really, but we also use this member to
8438                    mean "line isn't going to change" (Crafty
8439                    isn't searching, so stats won't change) */
8440                 programStats.line_is_book = 1;
8441
8442                 SendProgramStatsToFrontend( cps, &programStats );
8443
8444                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8445                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8446                     DisplayMove(currentMove - 1);
8447                 }
8448                 return;
8449             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8450                               &time, &nodes, &plylev, &mvleft,
8451                               &mvtot, mvname) >= 5) {
8452                 /* The stat01: line is from Crafty (9.29+) in response
8453                    to the "." command */
8454                 programStats.seen_stat = 1;
8455                 cps->maybeThinking = TRUE;
8456
8457                 if (programStats.got_only_move || !appData.periodicUpdates)
8458                   return;
8459
8460                 programStats.depth = plylev;
8461                 programStats.time = time;
8462                 programStats.nodes = nodes;
8463                 programStats.moves_left = mvleft;
8464                 programStats.nr_moves = mvtot;
8465                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8466                 programStats.ok_to_send = 1;
8467                 programStats.movelist[0] = '\0';
8468
8469                 SendProgramStatsToFrontend( cps, &programStats );
8470
8471                 return;
8472
8473             } else if (strncmp(message,"++",2) == 0) {
8474                 /* Crafty 9.29+ outputs this */
8475                 programStats.got_fail = 2;
8476                 return;
8477
8478             } else if (strncmp(message,"--",2) == 0) {
8479                 /* Crafty 9.29+ outputs this */
8480                 programStats.got_fail = 1;
8481                 return;
8482
8483             } else if (thinkOutput[0] != NULLCHAR &&
8484                        strncmp(message, "    ", 4) == 0) {
8485                 unsigned message_len;
8486
8487                 p = message;
8488                 while (*p && *p == ' ') p++;
8489
8490                 message_len = strlen( p );
8491
8492                 /* [AS] Avoid buffer overflow */
8493                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8494                     strcat(thinkOutput, " ");
8495                     strcat(thinkOutput, p);
8496                 }
8497
8498                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8499                     strcat(programStats.movelist, " ");
8500                     strcat(programStats.movelist, p);
8501                 }
8502
8503                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8504                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8505                     DisplayMove(currentMove - 1);
8506                 }
8507                 return;
8508             }
8509         }
8510         else {
8511             buf1[0] = NULLCHAR;
8512
8513             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8514                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8515             {
8516                 ChessProgramStats cpstats;
8517
8518                 if (plyext != ' ' && plyext != '\t') {
8519                     time *= 100;
8520                 }
8521
8522                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8523                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8524                     curscore = -curscore;
8525                 }
8526
8527                 cpstats.depth = plylev;
8528                 cpstats.nodes = nodes;
8529                 cpstats.time = time;
8530                 cpstats.score = curscore;
8531                 cpstats.got_only_move = 0;
8532                 cpstats.movelist[0] = '\0';
8533
8534                 if (buf1[0] != NULLCHAR) {
8535                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8536                 }
8537
8538                 cpstats.ok_to_send = 0;
8539                 cpstats.line_is_book = 0;
8540                 cpstats.nr_moves = 0;
8541                 cpstats.moves_left = 0;
8542
8543                 SendProgramStatsToFrontend( cps, &cpstats );
8544             }
8545         }
8546     }
8547 }
8548
8549
8550 /* Parse a game score from the character string "game", and
8551    record it as the history of the current game.  The game
8552    score is NOT assumed to start from the standard position.
8553    The display is not updated in any way.
8554    */
8555 void
8556 ParseGameHistory(game)
8557      char *game;
8558 {
8559     ChessMove moveType;
8560     int fromX, fromY, toX, toY, boardIndex;
8561     char promoChar;
8562     char *p, *q;
8563     char buf[MSG_SIZ];
8564
8565     if (appData.debugMode)
8566       fprintf(debugFP, "Parsing game history: %s\n", game);
8567
8568     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8569     gameInfo.site = StrSave(appData.icsHost);
8570     gameInfo.date = PGNDate();
8571     gameInfo.round = StrSave("-");
8572
8573     /* Parse out names of players */
8574     while (*game == ' ') game++;
8575     p = buf;
8576     while (*game != ' ') *p++ = *game++;
8577     *p = NULLCHAR;
8578     gameInfo.white = StrSave(buf);
8579     while (*game == ' ') game++;
8580     p = buf;
8581     while (*game != ' ' && *game != '\n') *p++ = *game++;
8582     *p = NULLCHAR;
8583     gameInfo.black = StrSave(buf);
8584
8585     /* Parse moves */
8586     boardIndex = blackPlaysFirst ? 1 : 0;
8587     yynewstr(game);
8588     for (;;) {
8589         yyboardindex = boardIndex;
8590         moveType = (ChessMove) Myylex();
8591         switch (moveType) {
8592           case IllegalMove:             /* maybe suicide chess, etc. */
8593   if (appData.debugMode) {
8594     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8595     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8596     setbuf(debugFP, NULL);
8597   }
8598           case WhitePromotion:
8599           case BlackPromotion:
8600           case WhiteNonPromotion:
8601           case BlackNonPromotion:
8602           case NormalMove:
8603           case WhiteCapturesEnPassant:
8604           case BlackCapturesEnPassant:
8605           case WhiteKingSideCastle:
8606           case WhiteQueenSideCastle:
8607           case BlackKingSideCastle:
8608           case BlackQueenSideCastle:
8609           case WhiteKingSideCastleWild:
8610           case WhiteQueenSideCastleWild:
8611           case BlackKingSideCastleWild:
8612           case BlackQueenSideCastleWild:
8613           /* PUSH Fabien */
8614           case WhiteHSideCastleFR:
8615           case WhiteASideCastleFR:
8616           case BlackHSideCastleFR:
8617           case BlackASideCastleFR:
8618           /* POP Fabien */
8619             fromX = currentMoveString[0] - AAA;
8620             fromY = currentMoveString[1] - ONE;
8621             toX = currentMoveString[2] - AAA;
8622             toY = currentMoveString[3] - ONE;
8623             promoChar = currentMoveString[4];
8624             break;
8625           case WhiteDrop:
8626           case BlackDrop:
8627             fromX = moveType == WhiteDrop ?
8628               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8629             (int) CharToPiece(ToLower(currentMoveString[0]));
8630             fromY = DROP_RANK;
8631             toX = currentMoveString[2] - AAA;
8632             toY = currentMoveString[3] - ONE;
8633             promoChar = NULLCHAR;
8634             break;
8635           case AmbiguousMove:
8636             /* bug? */
8637             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8638   if (appData.debugMode) {
8639     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8640     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8641     setbuf(debugFP, NULL);
8642   }
8643             DisplayError(buf, 0);
8644             return;
8645           case ImpossibleMove:
8646             /* bug? */
8647             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8648   if (appData.debugMode) {
8649     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8650     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8651     setbuf(debugFP, NULL);
8652   }
8653             DisplayError(buf, 0);
8654             return;
8655           case EndOfFile:
8656             if (boardIndex < backwardMostMove) {
8657                 /* Oops, gap.  How did that happen? */
8658                 DisplayError(_("Gap in move list"), 0);
8659                 return;
8660             }
8661             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8662             if (boardIndex > forwardMostMove) {
8663                 forwardMostMove = boardIndex;
8664             }
8665             return;
8666           case ElapsedTime:
8667             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8668                 strcat(parseList[boardIndex-1], " ");
8669                 strcat(parseList[boardIndex-1], yy_text);
8670             }
8671             continue;
8672           case Comment:
8673           case PGNTag:
8674           case NAG:
8675           default:
8676             /* ignore */
8677             continue;
8678           case WhiteWins:
8679           case BlackWins:
8680           case GameIsDrawn:
8681           case GameUnfinished:
8682             if (gameMode == IcsExamining) {
8683                 if (boardIndex < backwardMostMove) {
8684                     /* Oops, gap.  How did that happen? */
8685                     return;
8686                 }
8687                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8688                 return;
8689             }
8690             gameInfo.result = moveType;
8691             p = strchr(yy_text, '{');
8692             if (p == NULL) p = strchr(yy_text, '(');
8693             if (p == NULL) {
8694                 p = yy_text;
8695                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8696             } else {
8697                 q = strchr(p, *p == '{' ? '}' : ')');
8698                 if (q != NULL) *q = NULLCHAR;
8699                 p++;
8700             }
8701             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8702             gameInfo.resultDetails = StrSave(p);
8703             continue;
8704         }
8705         if (boardIndex >= forwardMostMove &&
8706             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8707             backwardMostMove = blackPlaysFirst ? 1 : 0;
8708             return;
8709         }
8710         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8711                                  fromY, fromX, toY, toX, promoChar,
8712                                  parseList[boardIndex]);
8713         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8714         /* currentMoveString is set as a side-effect of yylex */
8715         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8716         strcat(moveList[boardIndex], "\n");
8717         boardIndex++;
8718         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8719         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8720           case MT_NONE:
8721           case MT_STALEMATE:
8722           default:
8723             break;
8724           case MT_CHECK:
8725             if(gameInfo.variant != VariantShogi)
8726                 strcat(parseList[boardIndex - 1], "+");
8727             break;
8728           case MT_CHECKMATE:
8729           case MT_STAINMATE:
8730             strcat(parseList[boardIndex - 1], "#");
8731             break;
8732         }
8733     }
8734 }
8735
8736
8737 /* Apply a move to the given board  */
8738 void
8739 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8740      int fromX, fromY, toX, toY;
8741      int promoChar;
8742      Board board;
8743 {
8744   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8745   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8746
8747     /* [HGM] compute & store e.p. status and castling rights for new position */
8748     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8749
8750       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8751       oldEP = (signed char)board[EP_STATUS];
8752       board[EP_STATUS] = EP_NONE;
8753
8754       if( board[toY][toX] != EmptySquare )
8755            board[EP_STATUS] = EP_CAPTURE;
8756
8757   if (fromY == DROP_RANK) {
8758         /* must be first */
8759         piece = board[toY][toX] = (ChessSquare) fromX;
8760   } else {
8761       int i;
8762
8763       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8764            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8765                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8766       } else
8767       if( board[fromY][fromX] == WhitePawn ) {
8768            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8769                board[EP_STATUS] = EP_PAWN_MOVE;
8770            if( toY-fromY==2) {
8771                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8772                         gameInfo.variant != VariantBerolina || toX < fromX)
8773                       board[EP_STATUS] = toX | berolina;
8774                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8775                         gameInfo.variant != VariantBerolina || toX > fromX)
8776                       board[EP_STATUS] = toX;
8777            }
8778       } else
8779       if( board[fromY][fromX] == BlackPawn ) {
8780            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8781                board[EP_STATUS] = EP_PAWN_MOVE;
8782            if( toY-fromY== -2) {
8783                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8784                         gameInfo.variant != VariantBerolina || toX < fromX)
8785                       board[EP_STATUS] = toX | berolina;
8786                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8787                         gameInfo.variant != VariantBerolina || toX > fromX)
8788                       board[EP_STATUS] = toX;
8789            }
8790        }
8791
8792        for(i=0; i<nrCastlingRights; i++) {
8793            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8794               board[CASTLING][i] == toX   && castlingRank[i] == toY
8795              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8796        }
8797
8798      if (fromX == toX && fromY == toY) return;
8799
8800      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8801      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8802      if(gameInfo.variant == VariantKnightmate)
8803          king += (int) WhiteUnicorn - (int) WhiteKing;
8804
8805     /* Code added by Tord: */
8806     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8807     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8808         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8809       board[fromY][fromX] = EmptySquare;
8810       board[toY][toX] = EmptySquare;
8811       if((toX > fromX) != (piece == WhiteRook)) {
8812         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8813       } else {
8814         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8815       }
8816     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8817                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8818       board[fromY][fromX] = EmptySquare;
8819       board[toY][toX] = EmptySquare;
8820       if((toX > fromX) != (piece == BlackRook)) {
8821         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8822       } else {
8823         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8824       }
8825     /* End of code added by Tord */
8826
8827     } else if (board[fromY][fromX] == king
8828         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8829         && toY == fromY && toX > fromX+1) {
8830         board[fromY][fromX] = EmptySquare;
8831         board[toY][toX] = king;
8832         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8833         board[fromY][BOARD_RGHT-1] = EmptySquare;
8834     } else if (board[fromY][fromX] == king
8835         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8836                && toY == fromY && toX < fromX-1) {
8837         board[fromY][fromX] = EmptySquare;
8838         board[toY][toX] = king;
8839         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8840         board[fromY][BOARD_LEFT] = EmptySquare;
8841     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8842                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8843                && toY >= BOARD_HEIGHT-promoRank
8844                ) {
8845         /* white pawn promotion */
8846         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8847         if (board[toY][toX] == EmptySquare) {
8848             board[toY][toX] = WhiteQueen;
8849         }
8850         if(gameInfo.variant==VariantBughouse ||
8851            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8852             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8853         board[fromY][fromX] = EmptySquare;
8854     } else if ((fromY == BOARD_HEIGHT-4)
8855                && (toX != fromX)
8856                && gameInfo.variant != VariantXiangqi
8857                && gameInfo.variant != VariantBerolina
8858                && (board[fromY][fromX] == WhitePawn)
8859                && (board[toY][toX] == EmptySquare)) {
8860         board[fromY][fromX] = EmptySquare;
8861         board[toY][toX] = WhitePawn;
8862         captured = board[toY - 1][toX];
8863         board[toY - 1][toX] = EmptySquare;
8864     } else if ((fromY == BOARD_HEIGHT-4)
8865                && (toX == fromX)
8866                && gameInfo.variant == VariantBerolina
8867                && (board[fromY][fromX] == WhitePawn)
8868                && (board[toY][toX] == EmptySquare)) {
8869         board[fromY][fromX] = EmptySquare;
8870         board[toY][toX] = WhitePawn;
8871         if(oldEP & EP_BEROLIN_A) {
8872                 captured = board[fromY][fromX-1];
8873                 board[fromY][fromX-1] = EmptySquare;
8874         }else{  captured = board[fromY][fromX+1];
8875                 board[fromY][fromX+1] = EmptySquare;
8876         }
8877     } else if (board[fromY][fromX] == king
8878         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8879                && toY == fromY && toX > fromX+1) {
8880         board[fromY][fromX] = EmptySquare;
8881         board[toY][toX] = king;
8882         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8883         board[fromY][BOARD_RGHT-1] = EmptySquare;
8884     } else if (board[fromY][fromX] == king
8885         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8886                && toY == fromY && toX < fromX-1) {
8887         board[fromY][fromX] = EmptySquare;
8888         board[toY][toX] = king;
8889         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8890         board[fromY][BOARD_LEFT] = EmptySquare;
8891     } else if (fromY == 7 && fromX == 3
8892                && board[fromY][fromX] == BlackKing
8893                && toY == 7 && toX == 5) {
8894         board[fromY][fromX] = EmptySquare;
8895         board[toY][toX] = BlackKing;
8896         board[fromY][7] = EmptySquare;
8897         board[toY][4] = BlackRook;
8898     } else if (fromY == 7 && fromX == 3
8899                && board[fromY][fromX] == BlackKing
8900                && toY == 7 && toX == 1) {
8901         board[fromY][fromX] = EmptySquare;
8902         board[toY][toX] = BlackKing;
8903         board[fromY][0] = EmptySquare;
8904         board[toY][2] = BlackRook;
8905     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8906                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8907                && toY < promoRank
8908                ) {
8909         /* black pawn promotion */
8910         board[toY][toX] = CharToPiece(ToLower(promoChar));
8911         if (board[toY][toX] == EmptySquare) {
8912             board[toY][toX] = BlackQueen;
8913         }
8914         if(gameInfo.variant==VariantBughouse ||
8915            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8916             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8917         board[fromY][fromX] = EmptySquare;
8918     } else if ((fromY == 3)
8919                && (toX != fromX)
8920                && gameInfo.variant != VariantXiangqi
8921                && gameInfo.variant != VariantBerolina
8922                && (board[fromY][fromX] == BlackPawn)
8923                && (board[toY][toX] == EmptySquare)) {
8924         board[fromY][fromX] = EmptySquare;
8925         board[toY][toX] = BlackPawn;
8926         captured = board[toY + 1][toX];
8927         board[toY + 1][toX] = EmptySquare;
8928     } else if ((fromY == 3)
8929                && (toX == fromX)
8930                && gameInfo.variant == VariantBerolina
8931                && (board[fromY][fromX] == BlackPawn)
8932                && (board[toY][toX] == EmptySquare)) {
8933         board[fromY][fromX] = EmptySquare;
8934         board[toY][toX] = BlackPawn;
8935         if(oldEP & EP_BEROLIN_A) {
8936                 captured = board[fromY][fromX-1];
8937                 board[fromY][fromX-1] = EmptySquare;
8938         }else{  captured = board[fromY][fromX+1];
8939                 board[fromY][fromX+1] = EmptySquare;
8940         }
8941     } else {
8942         board[toY][toX] = board[fromY][fromX];
8943         board[fromY][fromX] = EmptySquare;
8944     }
8945   }
8946
8947     if (gameInfo.holdingsWidth != 0) {
8948
8949       /* !!A lot more code needs to be written to support holdings  */
8950       /* [HGM] OK, so I have written it. Holdings are stored in the */
8951       /* penultimate board files, so they are automaticlly stored   */
8952       /* in the game history.                                       */
8953       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8954                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8955         /* Delete from holdings, by decreasing count */
8956         /* and erasing image if necessary            */
8957         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8958         if(p < (int) BlackPawn) { /* white drop */
8959              p -= (int)WhitePawn;
8960                  p = PieceToNumber((ChessSquare)p);
8961              if(p >= gameInfo.holdingsSize) p = 0;
8962              if(--board[p][BOARD_WIDTH-2] <= 0)
8963                   board[p][BOARD_WIDTH-1] = EmptySquare;
8964              if((int)board[p][BOARD_WIDTH-2] < 0)
8965                         board[p][BOARD_WIDTH-2] = 0;
8966         } else {                  /* black drop */
8967              p -= (int)BlackPawn;
8968                  p = PieceToNumber((ChessSquare)p);
8969              if(p >= gameInfo.holdingsSize) p = 0;
8970              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8971                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8972              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8973                         board[BOARD_HEIGHT-1-p][1] = 0;
8974         }
8975       }
8976       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8977           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8978         /* [HGM] holdings: Add to holdings, if holdings exist */
8979         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8980                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8981                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8982         }
8983         p = (int) captured;
8984         if (p >= (int) BlackPawn) {
8985           p -= (int)BlackPawn;
8986           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8987                   /* in Shogi restore piece to its original  first */
8988                   captured = (ChessSquare) (DEMOTED captured);
8989                   p = DEMOTED p;
8990           }
8991           p = PieceToNumber((ChessSquare)p);
8992           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8993           board[p][BOARD_WIDTH-2]++;
8994           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8995         } else {
8996           p -= (int)WhitePawn;
8997           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8998                   captured = (ChessSquare) (DEMOTED captured);
8999                   p = DEMOTED p;
9000           }
9001           p = PieceToNumber((ChessSquare)p);
9002           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9003           board[BOARD_HEIGHT-1-p][1]++;
9004           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9005         }
9006       }
9007     } else if (gameInfo.variant == VariantAtomic) {
9008       if (captured != EmptySquare) {
9009         int y, x;
9010         for (y = toY-1; y <= toY+1; y++) {
9011           for (x = toX-1; x <= toX+1; x++) {
9012             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9013                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9014               board[y][x] = EmptySquare;
9015             }
9016           }
9017         }
9018         board[toY][toX] = EmptySquare;
9019       }
9020     }
9021     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9022         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9023     } else
9024     if(promoChar == '+') {
9025         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9026         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9027     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9028         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9029     }
9030     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9031                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9032         // [HGM] superchess: take promotion piece out of holdings
9033         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9034         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9035             if(!--board[k][BOARD_WIDTH-2])
9036                 board[k][BOARD_WIDTH-1] = EmptySquare;
9037         } else {
9038             if(!--board[BOARD_HEIGHT-1-k][1])
9039                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9040         }
9041     }
9042
9043 }
9044
9045 /* Updates forwardMostMove */
9046 void
9047 MakeMove(fromX, fromY, toX, toY, promoChar)
9048      int fromX, fromY, toX, toY;
9049      int promoChar;
9050 {
9051 //    forwardMostMove++; // [HGM] bare: moved downstream
9052
9053     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9054         int timeLeft; static int lastLoadFlag=0; int king, piece;
9055         piece = boards[forwardMostMove][fromY][fromX];
9056         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9057         if(gameInfo.variant == VariantKnightmate)
9058             king += (int) WhiteUnicorn - (int) WhiteKing;
9059         if(forwardMostMove == 0) {
9060             if(blackPlaysFirst)
9061                 fprintf(serverMoves, "%s;", second.tidy);
9062             fprintf(serverMoves, "%s;", first.tidy);
9063             if(!blackPlaysFirst)
9064                 fprintf(serverMoves, "%s;", second.tidy);
9065         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9066         lastLoadFlag = loadFlag;
9067         // print base move
9068         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9069         // print castling suffix
9070         if( toY == fromY && piece == king ) {
9071             if(toX-fromX > 1)
9072                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9073             if(fromX-toX >1)
9074                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9075         }
9076         // e.p. suffix
9077         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9078              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9079              boards[forwardMostMove][toY][toX] == EmptySquare
9080              && fromX != toX && fromY != toY)
9081                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9082         // promotion suffix
9083         if(promoChar != NULLCHAR)
9084                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9085         if(!loadFlag) {
9086             fprintf(serverMoves, "/%d/%d",
9087                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9088             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9089             else                      timeLeft = blackTimeRemaining/1000;
9090             fprintf(serverMoves, "/%d", timeLeft);
9091         }
9092         fflush(serverMoves);
9093     }
9094
9095     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9096       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9097                         0, 1);
9098       return;
9099     }
9100     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9101     if (commentList[forwardMostMove+1] != NULL) {
9102         free(commentList[forwardMostMove+1]);
9103         commentList[forwardMostMove+1] = NULL;
9104     }
9105     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9106     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9107     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9108     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9109     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9110     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9111     gameInfo.result = GameUnfinished;
9112     if (gameInfo.resultDetails != NULL) {
9113         free(gameInfo.resultDetails);
9114         gameInfo.resultDetails = NULL;
9115     }
9116     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9117                               moveList[forwardMostMove - 1]);
9118     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9119                              PosFlags(forwardMostMove - 1),
9120                              fromY, fromX, toY, toX, promoChar,
9121                              parseList[forwardMostMove - 1]);
9122     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9123       case MT_NONE:
9124       case MT_STALEMATE:
9125       default:
9126         break;
9127       case MT_CHECK:
9128         if(gameInfo.variant != VariantShogi)
9129             strcat(parseList[forwardMostMove - 1], "+");
9130         break;
9131       case MT_CHECKMATE:
9132       case MT_STAINMATE:
9133         strcat(parseList[forwardMostMove - 1], "#");
9134         break;
9135     }
9136     if (appData.debugMode) {
9137         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9138     }
9139
9140 }
9141
9142 /* Updates currentMove if not pausing */
9143 void
9144 ShowMove(fromX, fromY, toX, toY)
9145 {
9146     int instant = (gameMode == PlayFromGameFile) ?
9147         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9148     if(appData.noGUI) return;
9149     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9150         if (!instant) {
9151             if (forwardMostMove == currentMove + 1) {
9152                 AnimateMove(boards[forwardMostMove - 1],
9153                             fromX, fromY, toX, toY);
9154             }
9155             if (appData.highlightLastMove) {
9156                 SetHighlights(fromX, fromY, toX, toY);
9157             }
9158         }
9159         currentMove = forwardMostMove;
9160     }
9161
9162     if (instant) return;
9163
9164     DisplayMove(currentMove - 1);
9165     DrawPosition(FALSE, boards[currentMove]);
9166     DisplayBothClocks();
9167     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9168 }
9169
9170 void SendEgtPath(ChessProgramState *cps)
9171 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9172         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9173
9174         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9175
9176         while(*p) {
9177             char c, *q = name+1, *r, *s;
9178
9179             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9180             while(*p && *p != ',') *q++ = *p++;
9181             *q++ = ':'; *q = 0;
9182             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9183                 strcmp(name, ",nalimov:") == 0 ) {
9184                 // take nalimov path from the menu-changeable option first, if it is defined
9185               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9186                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9187             } else
9188             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9189                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9190                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9191                 s = r = StrStr(s, ":") + 1; // beginning of path info
9192                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9193                 c = *r; *r = 0;             // temporarily null-terminate path info
9194                     *--q = 0;               // strip of trailig ':' from name
9195                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9196                 *r = c;
9197                 SendToProgram(buf,cps);     // send egtbpath command for this format
9198             }
9199             if(*p == ',') p++; // read away comma to position for next format name
9200         }
9201 }
9202
9203 void
9204 InitChessProgram(cps, setup)
9205      ChessProgramState *cps;
9206      int setup; /* [HGM] needed to setup FRC opening position */
9207 {
9208     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9209     if (appData.noChessProgram) return;
9210     hintRequested = FALSE;
9211     bookRequested = FALSE;
9212
9213     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9214     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9215     if(cps->memSize) { /* [HGM] memory */
9216       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9217         SendToProgram(buf, cps);
9218     }
9219     SendEgtPath(cps); /* [HGM] EGT */
9220     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9221       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9222         SendToProgram(buf, cps);
9223     }
9224
9225     SendToProgram(cps->initString, cps);
9226     if (gameInfo.variant != VariantNormal &&
9227         gameInfo.variant != VariantLoadable
9228         /* [HGM] also send variant if board size non-standard */
9229         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9230                                             ) {
9231       char *v = VariantName(gameInfo.variant);
9232       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9233         /* [HGM] in protocol 1 we have to assume all variants valid */
9234         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9235         DisplayFatalError(buf, 0, 1);
9236         return;
9237       }
9238
9239       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9240       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9241       if( gameInfo.variant == VariantXiangqi )
9242            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9243       if( gameInfo.variant == VariantShogi )
9244            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9245       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9246            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9247       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9248           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9249            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9250       if( gameInfo.variant == VariantCourier )
9251            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9252       if( gameInfo.variant == VariantSuper )
9253            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9254       if( gameInfo.variant == VariantGreat )
9255            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9256       if( gameInfo.variant == VariantSChess )
9257            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9258
9259       if(overruled) {
9260         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9261                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9262            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9263            if(StrStr(cps->variants, b) == NULL) {
9264                // specific sized variant not known, check if general sizing allowed
9265                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9266                    if(StrStr(cps->variants, "boardsize") == NULL) {
9267                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9268                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9269                        DisplayFatalError(buf, 0, 1);
9270                        return;
9271                    }
9272                    /* [HGM] here we really should compare with the maximum supported board size */
9273                }
9274            }
9275       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9276       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9277       SendToProgram(buf, cps);
9278     }
9279     currentlyInitializedVariant = gameInfo.variant;
9280
9281     /* [HGM] send opening position in FRC to first engine */
9282     if(setup) {
9283           SendToProgram("force\n", cps);
9284           SendBoard(cps, 0);
9285           /* engine is now in force mode! Set flag to wake it up after first move. */
9286           setboardSpoiledMachineBlack = 1;
9287     }
9288
9289     if (cps->sendICS) {
9290       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9291       SendToProgram(buf, cps);
9292     }
9293     cps->maybeThinking = FALSE;
9294     cps->offeredDraw = 0;
9295     if (!appData.icsActive) {
9296         SendTimeControl(cps, movesPerSession, timeControl,
9297                         timeIncrement, appData.searchDepth,
9298                         searchTime);
9299     }
9300     if (appData.showThinking
9301         // [HGM] thinking: four options require thinking output to be sent
9302         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9303                                 ) {
9304         SendToProgram("post\n", cps);
9305     }
9306     SendToProgram("hard\n", cps);
9307     if (!appData.ponderNextMove) {
9308         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9309            it without being sure what state we are in first.  "hard"
9310            is not a toggle, so that one is OK.
9311          */
9312         SendToProgram("easy\n", cps);
9313     }
9314     if (cps->usePing) {
9315       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9316       SendToProgram(buf, cps);
9317     }
9318     cps->initDone = TRUE;
9319 }
9320
9321
9322 void
9323 StartChessProgram(cps)
9324      ChessProgramState *cps;
9325 {
9326     char buf[MSG_SIZ];
9327     int err;
9328
9329     if (appData.noChessProgram) return;
9330     cps->initDone = FALSE;
9331
9332     if (strcmp(cps->host, "localhost") == 0) {
9333         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9334     } else if (*appData.remoteShell == NULLCHAR) {
9335         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9336     } else {
9337         if (*appData.remoteUser == NULLCHAR) {
9338           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9339                     cps->program);
9340         } else {
9341           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9342                     cps->host, appData.remoteUser, cps->program);
9343         }
9344         err = StartChildProcess(buf, "", &cps->pr);
9345     }
9346
9347     if (err != 0) {
9348       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9349         DisplayFatalError(buf, err, 1);
9350         cps->pr = NoProc;
9351         cps->isr = NULL;
9352         return;
9353     }
9354
9355     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9356     if (cps->protocolVersion > 1) {
9357       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9358       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9359       cps->comboCnt = 0;  //                and values of combo boxes
9360       SendToProgram(buf, cps);
9361     } else {
9362       SendToProgram("xboard\n", cps);
9363     }
9364 }
9365
9366 void
9367 TwoMachinesEventIfReady P((void))
9368 {
9369   static int curMess = 0;
9370   if (first.lastPing != first.lastPong) {
9371     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9372     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9373     return;
9374   }
9375   if (second.lastPing != second.lastPong) {
9376     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9377     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9378     return;
9379   }
9380   DisplayMessage("", ""); curMess = 0;
9381   ThawUI();
9382   TwoMachinesEvent();
9383 }
9384
9385 int
9386 CreateTourney(char *name)
9387 {
9388         FILE *f;
9389         if(name[0] == NULLCHAR) return 0;
9390         f = fopen(appData.tourneyFile, "r");
9391         if(f) { // file exists
9392             ParseArgsFromFile(f); // parse it
9393         } else {
9394             f = fopen(appData.tourneyFile, "w");
9395             if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9396                 // create a file with tournament description
9397                 fprintf(f, "-participants {%s}\n", appData.participants);
9398                 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9399                 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9400                 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9401                 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9402                 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9403                 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9404                 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9405                 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9406                 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9407                 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9408                 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9409                 fprintf(f, "-results \"\"\n");
9410             }
9411         }
9412         fclose(f);
9413         appData.noChessProgram = FALSE;
9414         appData.clockMode = TRUE;
9415         SetGNUMode();
9416         return 1;
9417 }
9418
9419 #define MAXENGINES 1000
9420 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9421
9422 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9423 {
9424     char buf[MSG_SIZ], *p, *q;
9425     int i=1;
9426     while(*names) {
9427         p = names; q = buf;
9428         while(*p && *p != '\n') *q++ = *p++;
9429         *q = 0;
9430         if(engineList[i]) free(engineList[i]);
9431         engineList[i] = strdup(buf);
9432         if(*p == '\n') p++;
9433         TidyProgramName(engineList[i], "localhost", buf);
9434         if(engineMnemonic[i]) free(engineMnemonic[i]);
9435         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9436             strcat(buf, " (");
9437             sscanf(q + 8, "%s", buf + strlen(buf));
9438             strcat(buf, ")");
9439         }
9440         engineMnemonic[i] = strdup(buf);
9441         names = p; i++;
9442       if(i > MAXENGINES - 2) break;
9443     }
9444     engineList[i] = NULL;
9445 }
9446
9447 // following implemented as macro to avoid type limitations
9448 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9449
9450 void SwapEngines(int n)
9451 {   // swap settings for first engine and other engine (so far only some selected options)
9452     int h;
9453     char *p;
9454     if(n == 0) return;
9455     SWAP(directory, p)
9456     SWAP(chessProgram, p)
9457     SWAP(isUCI, h)
9458     SWAP(hasOwnBookUCI, h)
9459     SWAP(protocolVersion, h)
9460     SWAP(reuse, h)
9461     SWAP(scoreIsAbsolute, h)
9462     SWAP(timeOdds, h)
9463     SWAP(logo, p)
9464 }
9465
9466 void
9467 SetPlayer(int player)
9468 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9469     int i;
9470     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9471     static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9472                                  "-firstNeedsNoncompliantFEN false -firstNPS -1";
9473     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9474     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9475     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9476     if(mnemonic[i]) {
9477         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9478         ParseArgsFromString(resetOptions);
9479         ParseArgsFromString(buf);
9480     }
9481     free(engineName);
9482 }
9483
9484 int
9485 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9486 {   // determine players from game number
9487     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9488
9489     if(appData.tourneyType == 0) {
9490         roundsPerCycle = (nPlayers - 1) | 1;
9491         pairingsPerRound = nPlayers / 2;
9492     } else if(appData.tourneyType > 0) {
9493         roundsPerCycle = nPlayers - appData.tourneyType;
9494         pairingsPerRound = appData.tourneyType;
9495     }
9496     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9497     gamesPerCycle = gamesPerRound * roundsPerCycle;
9498     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9499     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9500     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9501     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9502     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9503     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9504
9505     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9506     if(appData.roundSync) *syncInterval = gamesPerRound;
9507
9508     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9509
9510     if(appData.tourneyType == 0) {
9511         if(curPairing == (nPlayers-1)/2 ) {
9512             *whitePlayer = curRound;
9513             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9514         } else {
9515             *whitePlayer = curRound - pairingsPerRound + curPairing;
9516             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9517             *blackPlayer = curRound + pairingsPerRound - curPairing;
9518             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9519         }
9520     } else if(appData.tourneyType > 0) {
9521         *whitePlayer = curPairing;
9522         *blackPlayer = curRound + appData.tourneyType;
9523     }
9524
9525     // take care of white/black alternation per round. 
9526     // For cycles and games this is already taken care of by default, derived from matchGame!
9527     return curRound & 1;
9528 }
9529
9530 int
9531 NextTourneyGame(int nr, int *swapColors)
9532 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9533     char *p, *q;
9534     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9535     FILE *tf;
9536     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9537     tf = fopen(appData.tourneyFile, "r");
9538     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9539     ParseArgsFromFile(tf); fclose(tf);
9540
9541     p = appData.participants;
9542     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9543     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9544
9545     if(syncInterval) {
9546         p = q = appData.results;
9547         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9548         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9549             DisplayMessage(_("Waiting for other game(s)"),"");
9550             waitingForGame = TRUE;
9551             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9552             return 0;
9553         }
9554         waitingForGame = FALSE;
9555     }
9556
9557     if(first.pr != NoProc) return 1; // engines already loaded
9558
9559     // redefine engines, engine dir, etc.
9560     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9561     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9562     SwapEngines(1);
9563     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9564     SwapEngines(1);         // and make that valid for second engine by swapping
9565     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9566     InitEngine(&second, 1);
9567     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9568     return 1;
9569 }
9570
9571 void
9572 NextMatchGame()
9573 {   // performs game initialization that does not invoke engines, and then tries to start the game
9574     int firstWhite, swapColors = 0;
9575     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9576     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9577     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9578     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9579     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9580     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9581     Reset(FALSE, first.pr != NoProc);
9582     appData.noChessProgram = FALSE;
9583     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9584     TwoMachinesEvent();
9585 }
9586
9587 void UserAdjudicationEvent( int result )
9588 {
9589     ChessMove gameResult = GameIsDrawn;
9590
9591     if( result > 0 ) {
9592         gameResult = WhiteWins;
9593     }
9594     else if( result < 0 ) {
9595         gameResult = BlackWins;
9596     }
9597
9598     if( gameMode == TwoMachinesPlay ) {
9599         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9600     }
9601 }
9602
9603
9604 // [HGM] save: calculate checksum of game to make games easily identifiable
9605 int StringCheckSum(char *s)
9606 {
9607         int i = 0;
9608         if(s==NULL) return 0;
9609         while(*s) i = i*259 + *s++;
9610         return i;
9611 }
9612
9613 int GameCheckSum()
9614 {
9615         int i, sum=0;
9616         for(i=backwardMostMove; i<forwardMostMove; i++) {
9617                 sum += pvInfoList[i].depth;
9618                 sum += StringCheckSum(parseList[i]);
9619                 sum += StringCheckSum(commentList[i]);
9620                 sum *= 261;
9621         }
9622         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9623         return sum + StringCheckSum(commentList[i]);
9624 } // end of save patch
9625
9626 void
9627 GameEnds(result, resultDetails, whosays)
9628      ChessMove result;
9629      char *resultDetails;
9630      int whosays;
9631 {
9632     GameMode nextGameMode;
9633     int isIcsGame;
9634     char buf[MSG_SIZ], popupRequested = 0, forceUnload;
9635
9636     if(endingGame) return; /* [HGM] crash: forbid recursion */
9637     endingGame = 1;
9638     if(twoBoards) { // [HGM] dual: switch back to one board
9639         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9640         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9641     }
9642     if (appData.debugMode) {
9643       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9644               result, resultDetails ? resultDetails : "(null)", whosays);
9645     }
9646
9647     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9648
9649     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9650         /* If we are playing on ICS, the server decides when the
9651            game is over, but the engine can offer to draw, claim
9652            a draw, or resign.
9653          */
9654 #if ZIPPY
9655         if (appData.zippyPlay && first.initDone) {
9656             if (result == GameIsDrawn) {
9657                 /* In case draw still needs to be claimed */
9658                 SendToICS(ics_prefix);
9659                 SendToICS("draw\n");
9660             } else if (StrCaseStr(resultDetails, "resign")) {
9661                 SendToICS(ics_prefix);
9662                 SendToICS("resign\n");
9663             }
9664         }
9665 #endif
9666         endingGame = 0; /* [HGM] crash */
9667         return;
9668     }
9669
9670     /* If we're loading the game from a file, stop */
9671     if (whosays == GE_FILE) {
9672       (void) StopLoadGameTimer();
9673       gameFileFP = NULL;
9674     }
9675
9676     /* Cancel draw offers */
9677     first.offeredDraw = second.offeredDraw = 0;
9678
9679     /* If this is an ICS game, only ICS can really say it's done;
9680        if not, anyone can. */
9681     isIcsGame = (gameMode == IcsPlayingWhite ||
9682                  gameMode == IcsPlayingBlack ||
9683                  gameMode == IcsObserving    ||
9684                  gameMode == IcsExamining);
9685
9686     if (!isIcsGame || whosays == GE_ICS) {
9687         /* OK -- not an ICS game, or ICS said it was done */
9688         StopClocks();
9689         if (!isIcsGame && !appData.noChessProgram)
9690           SetUserThinkingEnables();
9691
9692         /* [HGM] if a machine claims the game end we verify this claim */
9693         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9694             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9695                 char claimer;
9696                 ChessMove trueResult = (ChessMove) -1;
9697
9698                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9699                                             first.twoMachinesColor[0] :
9700                                             second.twoMachinesColor[0] ;
9701
9702                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9703                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9704                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9705                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9706                 } else
9707                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9708                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9709                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9710                 } else
9711                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9712                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9713                 }
9714
9715                 // now verify win claims, but not in drop games, as we don't understand those yet
9716                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9717                                                  || gameInfo.variant == VariantGreat) &&
9718                     (result == WhiteWins && claimer == 'w' ||
9719                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9720                       if (appData.debugMode) {
9721                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9722                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9723                       }
9724                       if(result != trueResult) {
9725                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9726                               result = claimer == 'w' ? BlackWins : WhiteWins;
9727                               resultDetails = buf;
9728                       }
9729                 } else
9730                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9731                     && (forwardMostMove <= backwardMostMove ||
9732                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9733                         (claimer=='b')==(forwardMostMove&1))
9734                                                                                   ) {
9735                       /* [HGM] verify: draws that were not flagged are false claims */
9736                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9737                       result = claimer == 'w' ? BlackWins : WhiteWins;
9738                       resultDetails = buf;
9739                 }
9740                 /* (Claiming a loss is accepted no questions asked!) */
9741             }
9742             /* [HGM] bare: don't allow bare King to win */
9743             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9744                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9745                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9746                && result != GameIsDrawn)
9747             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9748                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9749                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9750                         if(p >= 0 && p <= (int)WhiteKing) k++;
9751                 }
9752                 if (appData.debugMode) {
9753                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9754                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9755                 }
9756                 if(k <= 1) {
9757                         result = GameIsDrawn;
9758                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9759                         resultDetails = buf;
9760                 }
9761             }
9762         }
9763
9764
9765         if(serverMoves != NULL && !loadFlag) { char c = '=';
9766             if(result==WhiteWins) c = '+';
9767             if(result==BlackWins) c = '-';
9768             if(resultDetails != NULL)
9769                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9770         }
9771         if (resultDetails != NULL) {
9772             gameInfo.result = result;
9773             gameInfo.resultDetails = StrSave(resultDetails);
9774
9775             /* display last move only if game was not loaded from file */
9776             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9777                 DisplayMove(currentMove - 1);
9778
9779             if (forwardMostMove != 0) {
9780                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9781                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9782                                                                 ) {
9783                     if (*appData.saveGameFile != NULLCHAR) {
9784                         SaveGameToFile(appData.saveGameFile, TRUE);
9785                     } else if (appData.autoSaveGames) {
9786                         AutoSaveGame();
9787                     }
9788                     if (*appData.savePositionFile != NULLCHAR) {
9789                         SavePositionToFile(appData.savePositionFile);
9790                     }
9791                 }
9792             }
9793
9794             /* Tell program how game ended in case it is learning */
9795             /* [HGM] Moved this to after saving the PGN, just in case */
9796             /* engine died and we got here through time loss. In that */
9797             /* case we will get a fatal error writing the pipe, which */
9798             /* would otherwise lose us the PGN.                       */
9799             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9800             /* output during GameEnds should never be fatal anymore   */
9801             if (gameMode == MachinePlaysWhite ||
9802                 gameMode == MachinePlaysBlack ||
9803                 gameMode == TwoMachinesPlay ||
9804                 gameMode == IcsPlayingWhite ||
9805                 gameMode == IcsPlayingBlack ||
9806                 gameMode == BeginningOfGame) {
9807                 char buf[MSG_SIZ];
9808                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9809                         resultDetails);
9810                 if (first.pr != NoProc) {
9811                     SendToProgram(buf, &first);
9812                 }
9813                 if (second.pr != NoProc &&
9814                     gameMode == TwoMachinesPlay) {
9815                     SendToProgram(buf, &second);
9816                 }
9817             }
9818         }
9819
9820         if (appData.icsActive) {
9821             if (appData.quietPlay &&
9822                 (gameMode == IcsPlayingWhite ||
9823                  gameMode == IcsPlayingBlack)) {
9824                 SendToICS(ics_prefix);
9825                 SendToICS("set shout 1\n");
9826             }
9827             nextGameMode = IcsIdle;
9828             ics_user_moved = FALSE;
9829             /* clean up premove.  It's ugly when the game has ended and the
9830              * premove highlights are still on the board.
9831              */
9832             if (gotPremove) {
9833               gotPremove = FALSE;
9834               ClearPremoveHighlights();
9835               DrawPosition(FALSE, boards[currentMove]);
9836             }
9837             if (whosays == GE_ICS) {
9838                 switch (result) {
9839                 case WhiteWins:
9840                     if (gameMode == IcsPlayingWhite)
9841                         PlayIcsWinSound();
9842                     else if(gameMode == IcsPlayingBlack)
9843                         PlayIcsLossSound();
9844                     break;
9845                 case BlackWins:
9846                     if (gameMode == IcsPlayingBlack)
9847                         PlayIcsWinSound();
9848                     else if(gameMode == IcsPlayingWhite)
9849                         PlayIcsLossSound();
9850                     break;
9851                 case GameIsDrawn:
9852                     PlayIcsDrawSound();
9853                     break;
9854                 default:
9855                     PlayIcsUnfinishedSound();
9856                 }
9857             }
9858         } else if (gameMode == EditGame ||
9859                    gameMode == PlayFromGameFile ||
9860                    gameMode == AnalyzeMode ||
9861                    gameMode == AnalyzeFile) {
9862             nextGameMode = gameMode;
9863         } else {
9864             nextGameMode = EndOfGame;
9865         }
9866         pausing = FALSE;
9867         ModeHighlight();
9868     } else {
9869         nextGameMode = gameMode;
9870     }
9871
9872     if (appData.noChessProgram) {
9873         gameMode = nextGameMode;
9874         ModeHighlight();
9875         endingGame = 0; /* [HGM] crash */
9876         return;
9877     }
9878
9879     if (first.reuse) {
9880         /* Put first chess program into idle state */
9881         if (first.pr != NoProc &&
9882             (gameMode == MachinePlaysWhite ||
9883              gameMode == MachinePlaysBlack ||
9884              gameMode == TwoMachinesPlay ||
9885              gameMode == IcsPlayingWhite ||
9886              gameMode == IcsPlayingBlack ||
9887              gameMode == BeginningOfGame)) {
9888             SendToProgram("force\n", &first);
9889             if (first.usePing) {
9890               char buf[MSG_SIZ];
9891               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9892               SendToProgram(buf, &first);
9893             }
9894         }
9895     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9896         /* Kill off first chess program */
9897         if (first.isr != NULL)
9898           RemoveInputSource(first.isr);
9899         first.isr = NULL;
9900
9901         if (first.pr != NoProc) {
9902             ExitAnalyzeMode();
9903             DoSleep( appData.delayBeforeQuit );
9904             SendToProgram("quit\n", &first);
9905             DoSleep( appData.delayAfterQuit );
9906             DestroyChildProcess(first.pr, first.useSigterm);
9907         }
9908         first.pr = NoProc;
9909     }
9910     if (second.reuse) {
9911         /* Put second chess program into idle state */
9912         if (second.pr != NoProc &&
9913             gameMode == TwoMachinesPlay) {
9914             SendToProgram("force\n", &second);
9915             if (second.usePing) {
9916               char buf[MSG_SIZ];
9917               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9918               SendToProgram(buf, &second);
9919             }
9920         }
9921     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9922         /* Kill off second chess program */
9923         if (second.isr != NULL)
9924           RemoveInputSource(second.isr);
9925         second.isr = NULL;
9926
9927         if (second.pr != NoProc) {
9928             DoSleep( appData.delayBeforeQuit );
9929             SendToProgram("quit\n", &second);
9930             DoSleep( appData.delayAfterQuit );
9931             DestroyChildProcess(second.pr, second.useSigterm);
9932         }
9933         second.pr = NoProc;
9934     }
9935
9936     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
9937         char resChar = '=';
9938         switch (result) {
9939         case WhiteWins:
9940           resChar = '+';
9941           if (first.twoMachinesColor[0] == 'w') {
9942             first.matchWins++;
9943           } else {
9944             second.matchWins++;
9945           }
9946           break;
9947         case BlackWins:
9948           resChar = '-';
9949           if (first.twoMachinesColor[0] == 'b') {
9950             first.matchWins++;
9951           } else {
9952             second.matchWins++;
9953           }
9954           break;
9955         case GameUnfinished:
9956           resChar = ' ';
9957         default:
9958           break;
9959         }
9960
9961         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
9962         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
9963             ReserveGame(nextGame, resChar); // sets nextGame
9964             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0; // tourney is done
9965         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
9966
9967         if (nextGame <= appData.matchGames) {
9968             gameMode = nextGameMode;
9969             matchGame = nextGame; // this will be overruled in tourney mode!
9970             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
9971             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
9972             endingGame = 0; /* [HGM] crash */
9973             return;
9974         } else {
9975             gameMode = nextGameMode;
9976             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9977                      first.tidy, second.tidy,
9978                      first.matchWins, second.matchWins,
9979                      appData.matchGames - (first.matchWins + second.matchWins));
9980             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9981             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9982                 first.twoMachinesColor = "black\n";
9983                 second.twoMachinesColor = "white\n";
9984             } else {
9985                 first.twoMachinesColor = "white\n";
9986                 second.twoMachinesColor = "black\n";
9987             }
9988         }
9989     }
9990     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9991         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9992       ExitAnalyzeMode();
9993     gameMode = nextGameMode;
9994     ModeHighlight();
9995     endingGame = 0;  /* [HGM] crash */
9996     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9997       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9998         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
9999         DisplayNote(buf);
10000       }
10001     }
10002 }
10003
10004 /* Assumes program was just initialized (initString sent).
10005    Leaves program in force mode. */
10006 void
10007 FeedMovesToProgram(cps, upto)
10008      ChessProgramState *cps;
10009      int upto;
10010 {
10011     int i;
10012
10013     if (appData.debugMode)
10014       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10015               startedFromSetupPosition ? "position and " : "",
10016               backwardMostMove, upto, cps->which);
10017     if(currentlyInitializedVariant != gameInfo.variant) {
10018       char buf[MSG_SIZ];
10019         // [HGM] variantswitch: make engine aware of new variant
10020         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10021                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10022         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10023         SendToProgram(buf, cps);
10024         currentlyInitializedVariant = gameInfo.variant;
10025     }
10026     SendToProgram("force\n", cps);
10027     if (startedFromSetupPosition) {
10028         SendBoard(cps, backwardMostMove);
10029     if (appData.debugMode) {
10030         fprintf(debugFP, "feedMoves\n");
10031     }
10032     }
10033     for (i = backwardMostMove; i < upto; i++) {
10034         SendMoveToProgram(i, cps);
10035     }
10036 }
10037
10038
10039 int
10040 ResurrectChessProgram()
10041 {
10042      /* The chess program may have exited.
10043         If so, restart it and feed it all the moves made so far. */
10044     static int doInit = 0;
10045
10046     if (appData.noChessProgram) return 1;
10047
10048     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10049         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10050         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10051         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10052     } else {
10053         if (first.pr != NoProc) return 1;
10054         StartChessProgram(&first);
10055     }
10056     InitChessProgram(&first, FALSE);
10057     FeedMovesToProgram(&first, currentMove);
10058
10059     if (!first.sendTime) {
10060         /* can't tell gnuchess what its clock should read,
10061            so we bow to its notion. */
10062         ResetClocks();
10063         timeRemaining[0][currentMove] = whiteTimeRemaining;
10064         timeRemaining[1][currentMove] = blackTimeRemaining;
10065     }
10066
10067     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10068                 appData.icsEngineAnalyze) && first.analysisSupport) {
10069       SendToProgram("analyze\n", &first);
10070       first.analyzing = TRUE;
10071     }
10072     return 1;
10073 }
10074
10075 /*
10076  * Button procedures
10077  */
10078 void
10079 Reset(redraw, init)
10080      int redraw, init;
10081 {
10082     int i;
10083
10084     if (appData.debugMode) {
10085         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10086                 redraw, init, gameMode);
10087     }
10088     CleanupTail(); // [HGM] vari: delete any stored variations
10089     pausing = pauseExamInvalid = FALSE;
10090     startedFromSetupPosition = blackPlaysFirst = FALSE;
10091     firstMove = TRUE;
10092     whiteFlag = blackFlag = FALSE;
10093     userOfferedDraw = FALSE;
10094     hintRequested = bookRequested = FALSE;
10095     first.maybeThinking = FALSE;
10096     second.maybeThinking = FALSE;
10097     first.bookSuspend = FALSE; // [HGM] book
10098     second.bookSuspend = FALSE;
10099     thinkOutput[0] = NULLCHAR;
10100     lastHint[0] = NULLCHAR;
10101     ClearGameInfo(&gameInfo);
10102     gameInfo.variant = StringToVariant(appData.variant);
10103     ics_user_moved = ics_clock_paused = FALSE;
10104     ics_getting_history = H_FALSE;
10105     ics_gamenum = -1;
10106     white_holding[0] = black_holding[0] = NULLCHAR;
10107     ClearProgramStats();
10108     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10109
10110     ResetFrontEnd();
10111     ClearHighlights();
10112     flipView = appData.flipView;
10113     ClearPremoveHighlights();
10114     gotPremove = FALSE;
10115     alarmSounded = FALSE;
10116
10117     GameEnds(EndOfFile, NULL, GE_PLAYER);
10118     if(appData.serverMovesName != NULL) {
10119         /* [HGM] prepare to make moves file for broadcasting */
10120         clock_t t = clock();
10121         if(serverMoves != NULL) fclose(serverMoves);
10122         serverMoves = fopen(appData.serverMovesName, "r");
10123         if(serverMoves != NULL) {
10124             fclose(serverMoves);
10125             /* delay 15 sec before overwriting, so all clients can see end */
10126             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10127         }
10128         serverMoves = fopen(appData.serverMovesName, "w");
10129     }
10130
10131     ExitAnalyzeMode();
10132     gameMode = BeginningOfGame;
10133     ModeHighlight();
10134     if(appData.icsActive) gameInfo.variant = VariantNormal;
10135     currentMove = forwardMostMove = backwardMostMove = 0;
10136     InitPosition(redraw);
10137     for (i = 0; i < MAX_MOVES; i++) {
10138         if (commentList[i] != NULL) {
10139             free(commentList[i]);
10140             commentList[i] = NULL;
10141         }
10142     }
10143     ResetClocks();
10144     timeRemaining[0][0] = whiteTimeRemaining;
10145     timeRemaining[1][0] = blackTimeRemaining;
10146
10147     if (first.pr == NULL) {
10148         StartChessProgram(&first);
10149     }
10150     if (init) {
10151             InitChessProgram(&first, startedFromSetupPosition);
10152     }
10153     DisplayTitle("");
10154     DisplayMessage("", "");
10155     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10156     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10157 }
10158
10159 void
10160 AutoPlayGameLoop()
10161 {
10162     for (;;) {
10163         if (!AutoPlayOneMove())
10164           return;
10165         if (matchMode || appData.timeDelay == 0)
10166           continue;
10167         if (appData.timeDelay < 0)
10168           return;
10169         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10170         break;
10171     }
10172 }
10173
10174
10175 int
10176 AutoPlayOneMove()
10177 {
10178     int fromX, fromY, toX, toY;
10179
10180     if (appData.debugMode) {
10181       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10182     }
10183
10184     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10185       return FALSE;
10186
10187     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10188       pvInfoList[currentMove].depth = programStats.depth;
10189       pvInfoList[currentMove].score = programStats.score;
10190       pvInfoList[currentMove].time  = 0;
10191       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10192     }
10193
10194     if (currentMove >= forwardMostMove) {
10195       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10196       gameMode = EditGame;
10197       ModeHighlight();
10198
10199       /* [AS] Clear current move marker at the end of a game */
10200       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10201
10202       return FALSE;
10203     }
10204
10205     toX = moveList[currentMove][2] - AAA;
10206     toY = moveList[currentMove][3] - ONE;
10207
10208     if (moveList[currentMove][1] == '@') {
10209         if (appData.highlightLastMove) {
10210             SetHighlights(-1, -1, toX, toY);
10211         }
10212     } else {
10213         fromX = moveList[currentMove][0] - AAA;
10214         fromY = moveList[currentMove][1] - ONE;
10215
10216         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10217
10218         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10219
10220         if (appData.highlightLastMove) {
10221             SetHighlights(fromX, fromY, toX, toY);
10222         }
10223     }
10224     DisplayMove(currentMove);
10225     SendMoveToProgram(currentMove++, &first);
10226     DisplayBothClocks();
10227     DrawPosition(FALSE, boards[currentMove]);
10228     // [HGM] PV info: always display, routine tests if empty
10229     DisplayComment(currentMove - 1, commentList[currentMove]);
10230     return TRUE;
10231 }
10232
10233
10234 int
10235 LoadGameOneMove(readAhead)
10236      ChessMove readAhead;
10237 {
10238     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10239     char promoChar = NULLCHAR;
10240     ChessMove moveType;
10241     char move[MSG_SIZ];
10242     char *p, *q;
10243
10244     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10245         gameMode != AnalyzeMode && gameMode != Training) {
10246         gameFileFP = NULL;
10247         return FALSE;
10248     }
10249
10250     yyboardindex = forwardMostMove;
10251     if (readAhead != EndOfFile) {
10252       moveType = readAhead;
10253     } else {
10254       if (gameFileFP == NULL)
10255           return FALSE;
10256       moveType = (ChessMove) Myylex();
10257     }
10258
10259     done = FALSE;
10260     switch (moveType) {
10261       case Comment:
10262         if (appData.debugMode)
10263           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10264         p = yy_text;
10265
10266         /* append the comment but don't display it */
10267         AppendComment(currentMove, p, FALSE);
10268         return TRUE;
10269
10270       case WhiteCapturesEnPassant:
10271       case BlackCapturesEnPassant:
10272       case WhitePromotion:
10273       case BlackPromotion:
10274       case WhiteNonPromotion:
10275       case BlackNonPromotion:
10276       case NormalMove:
10277       case WhiteKingSideCastle:
10278       case WhiteQueenSideCastle:
10279       case BlackKingSideCastle:
10280       case BlackQueenSideCastle:
10281       case WhiteKingSideCastleWild:
10282       case WhiteQueenSideCastleWild:
10283       case BlackKingSideCastleWild:
10284       case BlackQueenSideCastleWild:
10285       /* PUSH Fabien */
10286       case WhiteHSideCastleFR:
10287       case WhiteASideCastleFR:
10288       case BlackHSideCastleFR:
10289       case BlackASideCastleFR:
10290       /* POP Fabien */
10291         if (appData.debugMode)
10292           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10293         fromX = currentMoveString[0] - AAA;
10294         fromY = currentMoveString[1] - ONE;
10295         toX = currentMoveString[2] - AAA;
10296         toY = currentMoveString[3] - ONE;
10297         promoChar = currentMoveString[4];
10298         break;
10299
10300       case WhiteDrop:
10301       case BlackDrop:
10302         if (appData.debugMode)
10303           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10304         fromX = moveType == WhiteDrop ?
10305           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10306         (int) CharToPiece(ToLower(currentMoveString[0]));
10307         fromY = DROP_RANK;
10308         toX = currentMoveString[2] - AAA;
10309         toY = currentMoveString[3] - ONE;
10310         break;
10311
10312       case WhiteWins:
10313       case BlackWins:
10314       case GameIsDrawn:
10315       case GameUnfinished:
10316         if (appData.debugMode)
10317           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10318         p = strchr(yy_text, '{');
10319         if (p == NULL) p = strchr(yy_text, '(');
10320         if (p == NULL) {
10321             p = yy_text;
10322             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10323         } else {
10324             q = strchr(p, *p == '{' ? '}' : ')');
10325             if (q != NULL) *q = NULLCHAR;
10326             p++;
10327         }
10328         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10329         GameEnds(moveType, p, GE_FILE);
10330         done = TRUE;
10331         if (cmailMsgLoaded) {
10332             ClearHighlights();
10333             flipView = WhiteOnMove(currentMove);
10334             if (moveType == GameUnfinished) flipView = !flipView;
10335             if (appData.debugMode)
10336               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10337         }
10338         break;
10339
10340       case EndOfFile:
10341         if (appData.debugMode)
10342           fprintf(debugFP, "Parser hit end of file\n");
10343         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10344           case MT_NONE:
10345           case MT_CHECK:
10346             break;
10347           case MT_CHECKMATE:
10348           case MT_STAINMATE:
10349             if (WhiteOnMove(currentMove)) {
10350                 GameEnds(BlackWins, "Black mates", GE_FILE);
10351             } else {
10352                 GameEnds(WhiteWins, "White mates", GE_FILE);
10353             }
10354             break;
10355           case MT_STALEMATE:
10356             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10357             break;
10358         }
10359         done = TRUE;
10360         break;
10361
10362       case MoveNumberOne:
10363         if (lastLoadGameStart == GNUChessGame) {
10364             /* GNUChessGames have numbers, but they aren't move numbers */
10365             if (appData.debugMode)
10366               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10367                       yy_text, (int) moveType);
10368             return LoadGameOneMove(EndOfFile); /* tail recursion */
10369         }
10370         /* else fall thru */
10371
10372       case XBoardGame:
10373       case GNUChessGame:
10374       case PGNTag:
10375         /* Reached start of next game in file */
10376         if (appData.debugMode)
10377           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10378         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10379           case MT_NONE:
10380           case MT_CHECK:
10381             break;
10382           case MT_CHECKMATE:
10383           case MT_STAINMATE:
10384             if (WhiteOnMove(currentMove)) {
10385                 GameEnds(BlackWins, "Black mates", GE_FILE);
10386             } else {
10387                 GameEnds(WhiteWins, "White mates", GE_FILE);
10388             }
10389             break;
10390           case MT_STALEMATE:
10391             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10392             break;
10393         }
10394         done = TRUE;
10395         break;
10396
10397       case PositionDiagram:     /* should not happen; ignore */
10398       case ElapsedTime:         /* ignore */
10399       case NAG:                 /* ignore */
10400         if (appData.debugMode)
10401           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10402                   yy_text, (int) moveType);
10403         return LoadGameOneMove(EndOfFile); /* tail recursion */
10404
10405       case IllegalMove:
10406         if (appData.testLegality) {
10407             if (appData.debugMode)
10408               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10409             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10410                     (forwardMostMove / 2) + 1,
10411                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10412             DisplayError(move, 0);
10413             done = TRUE;
10414         } else {
10415             if (appData.debugMode)
10416               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10417                       yy_text, currentMoveString);
10418             fromX = currentMoveString[0] - AAA;
10419             fromY = currentMoveString[1] - ONE;
10420             toX = currentMoveString[2] - AAA;
10421             toY = currentMoveString[3] - ONE;
10422             promoChar = currentMoveString[4];
10423         }
10424         break;
10425
10426       case AmbiguousMove:
10427         if (appData.debugMode)
10428           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10429         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10430                 (forwardMostMove / 2) + 1,
10431                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10432         DisplayError(move, 0);
10433         done = TRUE;
10434         break;
10435
10436       default:
10437       case ImpossibleMove:
10438         if (appData.debugMode)
10439           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10440         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10441                 (forwardMostMove / 2) + 1,
10442                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10443         DisplayError(move, 0);
10444         done = TRUE;
10445         break;
10446     }
10447
10448     if (done) {
10449         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10450             DrawPosition(FALSE, boards[currentMove]);
10451             DisplayBothClocks();
10452             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10453               DisplayComment(currentMove - 1, commentList[currentMove]);
10454         }
10455         (void) StopLoadGameTimer();
10456         gameFileFP = NULL;
10457         cmailOldMove = forwardMostMove;
10458         return FALSE;
10459     } else {
10460         /* currentMoveString is set as a side-effect of yylex */
10461
10462         thinkOutput[0] = NULLCHAR;
10463         MakeMove(fromX, fromY, toX, toY, promoChar);
10464         currentMove = forwardMostMove;
10465         return TRUE;
10466     }
10467 }
10468
10469 /* Load the nth game from the given file */
10470 int
10471 LoadGameFromFile(filename, n, title, useList)
10472      char *filename;
10473      int n;
10474      char *title;
10475      /*Boolean*/ int useList;
10476 {
10477     FILE *f;
10478     char buf[MSG_SIZ];
10479
10480     if (strcmp(filename, "-") == 0) {
10481         f = stdin;
10482         title = "stdin";
10483     } else {
10484         f = fopen(filename, "rb");
10485         if (f == NULL) {
10486           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10487             DisplayError(buf, errno);
10488             return FALSE;
10489         }
10490     }
10491     if (fseek(f, 0, 0) == -1) {
10492         /* f is not seekable; probably a pipe */
10493         useList = FALSE;
10494     }
10495     if (useList && n == 0) {
10496         int error = GameListBuild(f);
10497         if (error) {
10498             DisplayError(_("Cannot build game list"), error);
10499         } else if (!ListEmpty(&gameList) &&
10500                    ((ListGame *) gameList.tailPred)->number > 1) {
10501             GameListPopUp(f, title);
10502             return TRUE;
10503         }
10504         GameListDestroy();
10505         n = 1;
10506     }
10507     if (n == 0) n = 1;
10508     return LoadGame(f, n, title, FALSE);
10509 }
10510
10511
10512 void
10513 MakeRegisteredMove()
10514 {
10515     int fromX, fromY, toX, toY;
10516     char promoChar;
10517     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10518         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10519           case CMAIL_MOVE:
10520           case CMAIL_DRAW:
10521             if (appData.debugMode)
10522               fprintf(debugFP, "Restoring %s for game %d\n",
10523                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10524
10525             thinkOutput[0] = NULLCHAR;
10526             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10527             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10528             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10529             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10530             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10531             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10532             MakeMove(fromX, fromY, toX, toY, promoChar);
10533             ShowMove(fromX, fromY, toX, toY);
10534
10535             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10536               case MT_NONE:
10537               case MT_CHECK:
10538                 break;
10539
10540               case MT_CHECKMATE:
10541               case MT_STAINMATE:
10542                 if (WhiteOnMove(currentMove)) {
10543                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10544                 } else {
10545                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10546                 }
10547                 break;
10548
10549               case MT_STALEMATE:
10550                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10551                 break;
10552             }
10553
10554             break;
10555
10556           case CMAIL_RESIGN:
10557             if (WhiteOnMove(currentMove)) {
10558                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10559             } else {
10560                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10561             }
10562             break;
10563
10564           case CMAIL_ACCEPT:
10565             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10566             break;
10567
10568           default:
10569             break;
10570         }
10571     }
10572
10573     return;
10574 }
10575
10576 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10577 int
10578 CmailLoadGame(f, gameNumber, title, useList)
10579      FILE *f;
10580      int gameNumber;
10581      char *title;
10582      int useList;
10583 {
10584     int retVal;
10585
10586     if (gameNumber > nCmailGames) {
10587         DisplayError(_("No more games in this message"), 0);
10588         return FALSE;
10589     }
10590     if (f == lastLoadGameFP) {
10591         int offset = gameNumber - lastLoadGameNumber;
10592         if (offset == 0) {
10593             cmailMsg[0] = NULLCHAR;
10594             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10595                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10596                 nCmailMovesRegistered--;
10597             }
10598             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10599             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10600                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10601             }
10602         } else {
10603             if (! RegisterMove()) return FALSE;
10604         }
10605     }
10606
10607     retVal = LoadGame(f, gameNumber, title, useList);
10608
10609     /* Make move registered during previous look at this game, if any */
10610     MakeRegisteredMove();
10611
10612     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10613         commentList[currentMove]
10614           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10615         DisplayComment(currentMove - 1, commentList[currentMove]);
10616     }
10617
10618     return retVal;
10619 }
10620
10621 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10622 int
10623 ReloadGame(offset)
10624      int offset;
10625 {
10626     int gameNumber = lastLoadGameNumber + offset;
10627     if (lastLoadGameFP == NULL) {
10628         DisplayError(_("No game has been loaded yet"), 0);
10629         return FALSE;
10630     }
10631     if (gameNumber <= 0) {
10632         DisplayError(_("Can't back up any further"), 0);
10633         return FALSE;
10634     }
10635     if (cmailMsgLoaded) {
10636         return CmailLoadGame(lastLoadGameFP, gameNumber,
10637                              lastLoadGameTitle, lastLoadGameUseList);
10638     } else {
10639         return LoadGame(lastLoadGameFP, gameNumber,
10640                         lastLoadGameTitle, lastLoadGameUseList);
10641     }
10642 }
10643
10644
10645
10646 /* Load the nth game from open file f */
10647 int
10648 LoadGame(f, gameNumber, title, useList)
10649      FILE *f;
10650      int gameNumber;
10651      char *title;
10652      int useList;
10653 {
10654     ChessMove cm;
10655     char buf[MSG_SIZ];
10656     int gn = gameNumber;
10657     ListGame *lg = NULL;
10658     int numPGNTags = 0;
10659     int err;
10660     GameMode oldGameMode;
10661     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10662
10663     if (appData.debugMode)
10664         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10665
10666     if (gameMode == Training )
10667         SetTrainingModeOff();
10668
10669     oldGameMode = gameMode;
10670     if (gameMode != BeginningOfGame) {
10671       Reset(FALSE, TRUE);
10672     }
10673
10674     gameFileFP = f;
10675     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10676         fclose(lastLoadGameFP);
10677     }
10678
10679     if (useList) {
10680         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10681
10682         if (lg) {
10683             fseek(f, lg->offset, 0);
10684             GameListHighlight(gameNumber);
10685             gn = 1;
10686         }
10687         else {
10688             DisplayError(_("Game number out of range"), 0);
10689             return FALSE;
10690         }
10691     } else {
10692         GameListDestroy();
10693         if (fseek(f, 0, 0) == -1) {
10694             if (f == lastLoadGameFP ?
10695                 gameNumber == lastLoadGameNumber + 1 :
10696                 gameNumber == 1) {
10697                 gn = 1;
10698             } else {
10699                 DisplayError(_("Can't seek on game file"), 0);
10700                 return FALSE;
10701             }
10702         }
10703     }
10704     lastLoadGameFP = f;
10705     lastLoadGameNumber = gameNumber;
10706     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10707     lastLoadGameUseList = useList;
10708
10709     yynewfile(f);
10710
10711     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10712       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10713                 lg->gameInfo.black);
10714             DisplayTitle(buf);
10715     } else if (*title != NULLCHAR) {
10716         if (gameNumber > 1) {
10717           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10718             DisplayTitle(buf);
10719         } else {
10720             DisplayTitle(title);
10721         }
10722     }
10723
10724     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10725         gameMode = PlayFromGameFile;
10726         ModeHighlight();
10727     }
10728
10729     currentMove = forwardMostMove = backwardMostMove = 0;
10730     CopyBoard(boards[0], initialPosition);
10731     StopClocks();
10732
10733     /*
10734      * Skip the first gn-1 games in the file.
10735      * Also skip over anything that precedes an identifiable
10736      * start of game marker, to avoid being confused by
10737      * garbage at the start of the file.  Currently
10738      * recognized start of game markers are the move number "1",
10739      * the pattern "gnuchess .* game", the pattern
10740      * "^[#;%] [^ ]* game file", and a PGN tag block.
10741      * A game that starts with one of the latter two patterns
10742      * will also have a move number 1, possibly
10743      * following a position diagram.
10744      * 5-4-02: Let's try being more lenient and allowing a game to
10745      * start with an unnumbered move.  Does that break anything?
10746      */
10747     cm = lastLoadGameStart = EndOfFile;
10748     while (gn > 0) {
10749         yyboardindex = forwardMostMove;
10750         cm = (ChessMove) Myylex();
10751         switch (cm) {
10752           case EndOfFile:
10753             if (cmailMsgLoaded) {
10754                 nCmailGames = CMAIL_MAX_GAMES - gn;
10755             } else {
10756                 Reset(TRUE, TRUE);
10757                 DisplayError(_("Game not found in file"), 0);
10758             }
10759             return FALSE;
10760
10761           case GNUChessGame:
10762           case XBoardGame:
10763             gn--;
10764             lastLoadGameStart = cm;
10765             break;
10766
10767           case MoveNumberOne:
10768             switch (lastLoadGameStart) {
10769               case GNUChessGame:
10770               case XBoardGame:
10771               case PGNTag:
10772                 break;
10773               case MoveNumberOne:
10774               case EndOfFile:
10775                 gn--;           /* count this game */
10776                 lastLoadGameStart = cm;
10777                 break;
10778               default:
10779                 /* impossible */
10780                 break;
10781             }
10782             break;
10783
10784           case PGNTag:
10785             switch (lastLoadGameStart) {
10786               case GNUChessGame:
10787               case PGNTag:
10788               case MoveNumberOne:
10789               case EndOfFile:
10790                 gn--;           /* count this game */
10791                 lastLoadGameStart = cm;
10792                 break;
10793               case XBoardGame:
10794                 lastLoadGameStart = cm; /* game counted already */
10795                 break;
10796               default:
10797                 /* impossible */
10798                 break;
10799             }
10800             if (gn > 0) {
10801                 do {
10802                     yyboardindex = forwardMostMove;
10803                     cm = (ChessMove) Myylex();
10804                 } while (cm == PGNTag || cm == Comment);
10805             }
10806             break;
10807
10808           case WhiteWins:
10809           case BlackWins:
10810           case GameIsDrawn:
10811             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10812                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10813                     != CMAIL_OLD_RESULT) {
10814                     nCmailResults ++ ;
10815                     cmailResult[  CMAIL_MAX_GAMES
10816                                 - gn - 1] = CMAIL_OLD_RESULT;
10817                 }
10818             }
10819             break;
10820
10821           case NormalMove:
10822             /* Only a NormalMove can be at the start of a game
10823              * without a position diagram. */
10824             if (lastLoadGameStart == EndOfFile ) {
10825               gn--;
10826               lastLoadGameStart = MoveNumberOne;
10827             }
10828             break;
10829
10830           default:
10831             break;
10832         }
10833     }
10834
10835     if (appData.debugMode)
10836       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10837
10838     if (cm == XBoardGame) {
10839         /* Skip any header junk before position diagram and/or move 1 */
10840         for (;;) {
10841             yyboardindex = forwardMostMove;
10842             cm = (ChessMove) Myylex();
10843
10844             if (cm == EndOfFile ||
10845                 cm == GNUChessGame || cm == XBoardGame) {
10846                 /* Empty game; pretend end-of-file and handle later */
10847                 cm = EndOfFile;
10848                 break;
10849             }
10850
10851             if (cm == MoveNumberOne || cm == PositionDiagram ||
10852                 cm == PGNTag || cm == Comment)
10853               break;
10854         }
10855     } else if (cm == GNUChessGame) {
10856         if (gameInfo.event != NULL) {
10857             free(gameInfo.event);
10858         }
10859         gameInfo.event = StrSave(yy_text);
10860     }
10861
10862     startedFromSetupPosition = FALSE;
10863     while (cm == PGNTag) {
10864         if (appData.debugMode)
10865           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10866         err = ParsePGNTag(yy_text, &gameInfo);
10867         if (!err) numPGNTags++;
10868
10869         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10870         if(gameInfo.variant != oldVariant) {
10871             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10872             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10873             InitPosition(TRUE);
10874             oldVariant = gameInfo.variant;
10875             if (appData.debugMode)
10876               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10877         }
10878
10879
10880         if (gameInfo.fen != NULL) {
10881           Board initial_position;
10882           startedFromSetupPosition = TRUE;
10883           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10884             Reset(TRUE, TRUE);
10885             DisplayError(_("Bad FEN position in file"), 0);
10886             return FALSE;
10887           }
10888           CopyBoard(boards[0], initial_position);
10889           if (blackPlaysFirst) {
10890             currentMove = forwardMostMove = backwardMostMove = 1;
10891             CopyBoard(boards[1], initial_position);
10892             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10893             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10894             timeRemaining[0][1] = whiteTimeRemaining;
10895             timeRemaining[1][1] = blackTimeRemaining;
10896             if (commentList[0] != NULL) {
10897               commentList[1] = commentList[0];
10898               commentList[0] = NULL;
10899             }
10900           } else {
10901             currentMove = forwardMostMove = backwardMostMove = 0;
10902           }
10903           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10904           {   int i;
10905               initialRulePlies = FENrulePlies;
10906               for( i=0; i< nrCastlingRights; i++ )
10907                   initialRights[i] = initial_position[CASTLING][i];
10908           }
10909           yyboardindex = forwardMostMove;
10910           free(gameInfo.fen);
10911           gameInfo.fen = NULL;
10912         }
10913
10914         yyboardindex = forwardMostMove;
10915         cm = (ChessMove) Myylex();
10916
10917         /* Handle comments interspersed among the tags */
10918         while (cm == Comment) {
10919             char *p;
10920             if (appData.debugMode)
10921               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10922             p = yy_text;
10923             AppendComment(currentMove, p, FALSE);
10924             yyboardindex = forwardMostMove;
10925             cm = (ChessMove) Myylex();
10926         }
10927     }
10928
10929     /* don't rely on existence of Event tag since if game was
10930      * pasted from clipboard the Event tag may not exist
10931      */
10932     if (numPGNTags > 0){
10933         char *tags;
10934         if (gameInfo.variant == VariantNormal) {
10935           VariantClass v = StringToVariant(gameInfo.event);
10936           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10937           if(v < VariantShogi) gameInfo.variant = v;
10938         }
10939         if (!matchMode) {
10940           if( appData.autoDisplayTags ) {
10941             tags = PGNTags(&gameInfo);
10942             TagsPopUp(tags, CmailMsg());
10943             free(tags);
10944           }
10945         }
10946     } else {
10947         /* Make something up, but don't display it now */
10948         SetGameInfo();
10949         TagsPopDown();
10950     }
10951
10952     if (cm == PositionDiagram) {
10953         int i, j;
10954         char *p;
10955         Board initial_position;
10956
10957         if (appData.debugMode)
10958           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10959
10960         if (!startedFromSetupPosition) {
10961             p = yy_text;
10962             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10963               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10964                 switch (*p) {
10965                   case '{':
10966                   case '[':
10967                   case '-':
10968                   case ' ':
10969                   case '\t':
10970                   case '\n':
10971                   case '\r':
10972                     break;
10973                   default:
10974                     initial_position[i][j++] = CharToPiece(*p);
10975                     break;
10976                 }
10977             while (*p == ' ' || *p == '\t' ||
10978                    *p == '\n' || *p == '\r') p++;
10979
10980             if (strncmp(p, "black", strlen("black"))==0)
10981               blackPlaysFirst = TRUE;
10982             else
10983               blackPlaysFirst = FALSE;
10984             startedFromSetupPosition = TRUE;
10985
10986             CopyBoard(boards[0], initial_position);
10987             if (blackPlaysFirst) {
10988                 currentMove = forwardMostMove = backwardMostMove = 1;
10989                 CopyBoard(boards[1], initial_position);
10990                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10991                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10992                 timeRemaining[0][1] = whiteTimeRemaining;
10993                 timeRemaining[1][1] = blackTimeRemaining;
10994                 if (commentList[0] != NULL) {
10995                     commentList[1] = commentList[0];
10996                     commentList[0] = NULL;
10997                 }
10998             } else {
10999                 currentMove = forwardMostMove = backwardMostMove = 0;
11000             }
11001         }
11002         yyboardindex = forwardMostMove;
11003         cm = (ChessMove) Myylex();
11004     }
11005
11006     if (first.pr == NoProc) {
11007         StartChessProgram(&first);
11008     }
11009     InitChessProgram(&first, FALSE);
11010     SendToProgram("force\n", &first);
11011     if (startedFromSetupPosition) {
11012         SendBoard(&first, forwardMostMove);
11013     if (appData.debugMode) {
11014         fprintf(debugFP, "Load Game\n");
11015     }
11016         DisplayBothClocks();
11017     }
11018
11019     /* [HGM] server: flag to write setup moves in broadcast file as one */
11020     loadFlag = appData.suppressLoadMoves;
11021
11022     while (cm == Comment) {
11023         char *p;
11024         if (appData.debugMode)
11025           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11026         p = yy_text;
11027         AppendComment(currentMove, p, FALSE);
11028         yyboardindex = forwardMostMove;
11029         cm = (ChessMove) Myylex();
11030     }
11031
11032     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11033         cm == WhiteWins || cm == BlackWins ||
11034         cm == GameIsDrawn || cm == GameUnfinished) {
11035         DisplayMessage("", _("No moves in game"));
11036         if (cmailMsgLoaded) {
11037             if (appData.debugMode)
11038               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11039             ClearHighlights();
11040             flipView = FALSE;
11041         }
11042         DrawPosition(FALSE, boards[currentMove]);
11043         DisplayBothClocks();
11044         gameMode = EditGame;
11045         ModeHighlight();
11046         gameFileFP = NULL;
11047         cmailOldMove = 0;
11048         return TRUE;
11049     }
11050
11051     // [HGM] PV info: routine tests if comment empty
11052     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11053         DisplayComment(currentMove - 1, commentList[currentMove]);
11054     }
11055     if (!matchMode && appData.timeDelay != 0)
11056       DrawPosition(FALSE, boards[currentMove]);
11057
11058     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11059       programStats.ok_to_send = 1;
11060     }
11061
11062     /* if the first token after the PGN tags is a move
11063      * and not move number 1, retrieve it from the parser
11064      */
11065     if (cm != MoveNumberOne)
11066         LoadGameOneMove(cm);
11067
11068     /* load the remaining moves from the file */
11069     while (LoadGameOneMove(EndOfFile)) {
11070       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11071       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11072     }
11073
11074     /* rewind to the start of the game */
11075     currentMove = backwardMostMove;
11076
11077     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11078
11079     if (oldGameMode == AnalyzeFile ||
11080         oldGameMode == AnalyzeMode) {
11081       AnalyzeFileEvent();
11082     }
11083
11084     if (matchMode || appData.timeDelay == 0) {
11085       ToEndEvent();
11086       gameMode = EditGame;
11087       ModeHighlight();
11088     } else if (appData.timeDelay > 0) {
11089       AutoPlayGameLoop();
11090     }
11091
11092     if (appData.debugMode)
11093         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11094
11095     loadFlag = 0; /* [HGM] true game starts */
11096     return TRUE;
11097 }
11098
11099 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11100 int
11101 ReloadPosition(offset)
11102      int offset;
11103 {
11104     int positionNumber = lastLoadPositionNumber + offset;
11105     if (lastLoadPositionFP == NULL) {
11106         DisplayError(_("No position has been loaded yet"), 0);
11107         return FALSE;
11108     }
11109     if (positionNumber <= 0) {
11110         DisplayError(_("Can't back up any further"), 0);
11111         return FALSE;
11112     }
11113     return LoadPosition(lastLoadPositionFP, positionNumber,
11114                         lastLoadPositionTitle);
11115 }
11116
11117 /* Load the nth position from the given file */
11118 int
11119 LoadPositionFromFile(filename, n, title)
11120      char *filename;
11121      int n;
11122      char *title;
11123 {
11124     FILE *f;
11125     char buf[MSG_SIZ];
11126
11127     if (strcmp(filename, "-") == 0) {
11128         return LoadPosition(stdin, n, "stdin");
11129     } else {
11130         f = fopen(filename, "rb");
11131         if (f == NULL) {
11132             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11133             DisplayError(buf, errno);
11134             return FALSE;
11135         } else {
11136             return LoadPosition(f, n, title);
11137         }
11138     }
11139 }
11140
11141 /* Load the nth position from the given open file, and close it */
11142 int
11143 LoadPosition(f, positionNumber, title)
11144      FILE *f;
11145      int positionNumber;
11146      char *title;
11147 {
11148     char *p, line[MSG_SIZ];
11149     Board initial_position;
11150     int i, j, fenMode, pn;
11151
11152     if (gameMode == Training )
11153         SetTrainingModeOff();
11154
11155     if (gameMode != BeginningOfGame) {
11156         Reset(FALSE, TRUE);
11157     }
11158     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11159         fclose(lastLoadPositionFP);
11160     }
11161     if (positionNumber == 0) positionNumber = 1;
11162     lastLoadPositionFP = f;
11163     lastLoadPositionNumber = positionNumber;
11164     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11165     if (first.pr == NoProc) {
11166       StartChessProgram(&first);
11167       InitChessProgram(&first, FALSE);
11168     }
11169     pn = positionNumber;
11170     if (positionNumber < 0) {
11171         /* Negative position number means to seek to that byte offset */
11172         if (fseek(f, -positionNumber, 0) == -1) {
11173             DisplayError(_("Can't seek on position file"), 0);
11174             return FALSE;
11175         };
11176         pn = 1;
11177     } else {
11178         if (fseek(f, 0, 0) == -1) {
11179             if (f == lastLoadPositionFP ?
11180                 positionNumber == lastLoadPositionNumber + 1 :
11181                 positionNumber == 1) {
11182                 pn = 1;
11183             } else {
11184                 DisplayError(_("Can't seek on position file"), 0);
11185                 return FALSE;
11186             }
11187         }
11188     }
11189     /* See if this file is FEN or old-style xboard */
11190     if (fgets(line, MSG_SIZ, f) == NULL) {
11191         DisplayError(_("Position not found in file"), 0);
11192         return FALSE;
11193     }
11194     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11195     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11196
11197     if (pn >= 2) {
11198         if (fenMode || line[0] == '#') pn--;
11199         while (pn > 0) {
11200             /* skip positions before number pn */
11201             if (fgets(line, MSG_SIZ, f) == NULL) {
11202                 Reset(TRUE, TRUE);
11203                 DisplayError(_("Position not found in file"), 0);
11204                 return FALSE;
11205             }
11206             if (fenMode || line[0] == '#') pn--;
11207         }
11208     }
11209
11210     if (fenMode) {
11211         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11212             DisplayError(_("Bad FEN position in file"), 0);
11213             return FALSE;
11214         }
11215     } else {
11216         (void) fgets(line, MSG_SIZ, f);
11217         (void) fgets(line, MSG_SIZ, f);
11218
11219         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11220             (void) fgets(line, MSG_SIZ, f);
11221             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11222                 if (*p == ' ')
11223                   continue;
11224                 initial_position[i][j++] = CharToPiece(*p);
11225             }
11226         }
11227
11228         blackPlaysFirst = FALSE;
11229         if (!feof(f)) {
11230             (void) fgets(line, MSG_SIZ, f);
11231             if (strncmp(line, "black", strlen("black"))==0)
11232               blackPlaysFirst = TRUE;
11233         }
11234     }
11235     startedFromSetupPosition = TRUE;
11236
11237     SendToProgram("force\n", &first);
11238     CopyBoard(boards[0], initial_position);
11239     if (blackPlaysFirst) {
11240         currentMove = forwardMostMove = backwardMostMove = 1;
11241         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11242         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11243         CopyBoard(boards[1], initial_position);
11244         DisplayMessage("", _("Black to play"));
11245     } else {
11246         currentMove = forwardMostMove = backwardMostMove = 0;
11247         DisplayMessage("", _("White to play"));
11248     }
11249     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11250     SendBoard(&first, forwardMostMove);
11251     if (appData.debugMode) {
11252 int i, j;
11253   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11254   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11255         fprintf(debugFP, "Load Position\n");
11256     }
11257
11258     if (positionNumber > 1) {
11259       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11260         DisplayTitle(line);
11261     } else {
11262         DisplayTitle(title);
11263     }
11264     gameMode = EditGame;
11265     ModeHighlight();
11266     ResetClocks();
11267     timeRemaining[0][1] = whiteTimeRemaining;
11268     timeRemaining[1][1] = blackTimeRemaining;
11269     DrawPosition(FALSE, boards[currentMove]);
11270
11271     return TRUE;
11272 }
11273
11274
11275 void
11276 CopyPlayerNameIntoFileName(dest, src)
11277      char **dest, *src;
11278 {
11279     while (*src != NULLCHAR && *src != ',') {
11280         if (*src == ' ') {
11281             *(*dest)++ = '_';
11282             src++;
11283         } else {
11284             *(*dest)++ = *src++;
11285         }
11286     }
11287 }
11288
11289 char *DefaultFileName(ext)
11290      char *ext;
11291 {
11292     static char def[MSG_SIZ];
11293     char *p;
11294
11295     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11296         p = def;
11297         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11298         *p++ = '-';
11299         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11300         *p++ = '.';
11301         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11302     } else {
11303         def[0] = NULLCHAR;
11304     }
11305     return def;
11306 }
11307
11308 /* Save the current game to the given file */
11309 int
11310 SaveGameToFile(filename, append)
11311      char *filename;
11312      int append;
11313 {
11314     FILE *f;
11315     char buf[MSG_SIZ];
11316     int result;
11317
11318     if (strcmp(filename, "-") == 0) {
11319         return SaveGame(stdout, 0, NULL);
11320     } else {
11321         f = fopen(filename, append ? "a" : "w");
11322         if (f == NULL) {
11323             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11324             DisplayError(buf, errno);
11325             return FALSE;
11326         } else {
11327             safeStrCpy(buf, lastMsg, MSG_SIZ);
11328             DisplayMessage(_("Waiting for access to save file"), "");
11329             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11330             DisplayMessage(_("Saving game"), "");
11331             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11332             result = SaveGame(f, 0, NULL);
11333             DisplayMessage(buf, "");
11334             return result;
11335         }
11336     }
11337 }
11338
11339 char *
11340 SavePart(str)
11341      char *str;
11342 {
11343     static char buf[MSG_SIZ];
11344     char *p;
11345
11346     p = strchr(str, ' ');
11347     if (p == NULL) return str;
11348     strncpy(buf, str, p - str);
11349     buf[p - str] = NULLCHAR;
11350     return buf;
11351 }
11352
11353 #define PGN_MAX_LINE 75
11354
11355 #define PGN_SIDE_WHITE  0
11356 #define PGN_SIDE_BLACK  1
11357
11358 /* [AS] */
11359 static int FindFirstMoveOutOfBook( int side )
11360 {
11361     int result = -1;
11362
11363     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11364         int index = backwardMostMove;
11365         int has_book_hit = 0;
11366
11367         if( (index % 2) != side ) {
11368             index++;
11369         }
11370
11371         while( index < forwardMostMove ) {
11372             /* Check to see if engine is in book */
11373             int depth = pvInfoList[index].depth;
11374             int score = pvInfoList[index].score;
11375             int in_book = 0;
11376
11377             if( depth <= 2 ) {
11378                 in_book = 1;
11379             }
11380             else if( score == 0 && depth == 63 ) {
11381                 in_book = 1; /* Zappa */
11382             }
11383             else if( score == 2 && depth == 99 ) {
11384                 in_book = 1; /* Abrok */
11385             }
11386
11387             has_book_hit += in_book;
11388
11389             if( ! in_book ) {
11390                 result = index;
11391
11392                 break;
11393             }
11394
11395             index += 2;
11396         }
11397     }
11398
11399     return result;
11400 }
11401
11402 /* [AS] */
11403 void GetOutOfBookInfo( char * buf )
11404 {
11405     int oob[2];
11406     int i;
11407     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11408
11409     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11410     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11411
11412     *buf = '\0';
11413
11414     if( oob[0] >= 0 || oob[1] >= 0 ) {
11415         for( i=0; i<2; i++ ) {
11416             int idx = oob[i];
11417
11418             if( idx >= 0 ) {
11419                 if( i > 0 && oob[0] >= 0 ) {
11420                     strcat( buf, "   " );
11421                 }
11422
11423                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11424                 sprintf( buf+strlen(buf), "%s%.2f",
11425                     pvInfoList[idx].score >= 0 ? "+" : "",
11426                     pvInfoList[idx].score / 100.0 );
11427             }
11428         }
11429     }
11430 }
11431
11432 /* Save game in PGN style and close the file */
11433 int
11434 SaveGamePGN(f)
11435      FILE *f;
11436 {
11437     int i, offset, linelen, newblock;
11438     time_t tm;
11439 //    char *movetext;
11440     char numtext[32];
11441     int movelen, numlen, blank;
11442     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11443
11444     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11445
11446     tm = time((time_t *) NULL);
11447
11448     PrintPGNTags(f, &gameInfo);
11449
11450     if (backwardMostMove > 0 || startedFromSetupPosition) {
11451         char *fen = PositionToFEN(backwardMostMove, NULL);
11452         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11453         fprintf(f, "\n{--------------\n");
11454         PrintPosition(f, backwardMostMove);
11455         fprintf(f, "--------------}\n");
11456         free(fen);
11457     }
11458     else {
11459         /* [AS] Out of book annotation */
11460         if( appData.saveOutOfBookInfo ) {
11461             char buf[64];
11462
11463             GetOutOfBookInfo( buf );
11464
11465             if( buf[0] != '\0' ) {
11466                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11467             }
11468         }
11469
11470         fprintf(f, "\n");
11471     }
11472
11473     i = backwardMostMove;
11474     linelen = 0;
11475     newblock = TRUE;
11476
11477     while (i < forwardMostMove) {
11478         /* Print comments preceding this move */
11479         if (commentList[i] != NULL) {
11480             if (linelen > 0) fprintf(f, "\n");
11481             fprintf(f, "%s", commentList[i]);
11482             linelen = 0;
11483             newblock = TRUE;
11484         }
11485
11486         /* Format move number */
11487         if ((i % 2) == 0)
11488           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11489         else
11490           if (newblock)
11491             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11492           else
11493             numtext[0] = NULLCHAR;
11494
11495         numlen = strlen(numtext);
11496         newblock = FALSE;
11497
11498         /* Print move number */
11499         blank = linelen > 0 && numlen > 0;
11500         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11501             fprintf(f, "\n");
11502             linelen = 0;
11503             blank = 0;
11504         }
11505         if (blank) {
11506             fprintf(f, " ");
11507             linelen++;
11508         }
11509         fprintf(f, "%s", numtext);
11510         linelen += numlen;
11511
11512         /* Get move */
11513         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11514         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11515
11516         /* Print move */
11517         blank = linelen > 0 && movelen > 0;
11518         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11519             fprintf(f, "\n");
11520             linelen = 0;
11521             blank = 0;
11522         }
11523         if (blank) {
11524             fprintf(f, " ");
11525             linelen++;
11526         }
11527         fprintf(f, "%s", move_buffer);
11528         linelen += movelen;
11529
11530         /* [AS] Add PV info if present */
11531         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11532             /* [HGM] add time */
11533             char buf[MSG_SIZ]; int seconds;
11534
11535             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11536
11537             if( seconds <= 0)
11538               buf[0] = 0;
11539             else
11540               if( seconds < 30 )
11541                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11542               else
11543                 {
11544                   seconds = (seconds + 4)/10; // round to full seconds
11545                   if( seconds < 60 )
11546                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11547                   else
11548                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11549                 }
11550
11551             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11552                       pvInfoList[i].score >= 0 ? "+" : "",
11553                       pvInfoList[i].score / 100.0,
11554                       pvInfoList[i].depth,
11555                       buf );
11556
11557             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11558
11559             /* Print score/depth */
11560             blank = linelen > 0 && movelen > 0;
11561             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11562                 fprintf(f, "\n");
11563                 linelen = 0;
11564                 blank = 0;
11565             }
11566             if (blank) {
11567                 fprintf(f, " ");
11568                 linelen++;
11569             }
11570             fprintf(f, "%s", move_buffer);
11571             linelen += movelen;
11572         }
11573
11574         i++;
11575     }
11576
11577     /* Start a new line */
11578     if (linelen > 0) fprintf(f, "\n");
11579
11580     /* Print comments after last move */
11581     if (commentList[i] != NULL) {
11582         fprintf(f, "%s\n", commentList[i]);
11583     }
11584
11585     /* Print result */
11586     if (gameInfo.resultDetails != NULL &&
11587         gameInfo.resultDetails[0] != NULLCHAR) {
11588         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11589                 PGNResult(gameInfo.result));
11590     } else {
11591         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11592     }
11593
11594     fclose(f);
11595     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11596     return TRUE;
11597 }
11598
11599 /* Save game in old style and close the file */
11600 int
11601 SaveGameOldStyle(f)
11602      FILE *f;
11603 {
11604     int i, offset;
11605     time_t tm;
11606
11607     tm = time((time_t *) NULL);
11608
11609     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11610     PrintOpponents(f);
11611
11612     if (backwardMostMove > 0 || startedFromSetupPosition) {
11613         fprintf(f, "\n[--------------\n");
11614         PrintPosition(f, backwardMostMove);
11615         fprintf(f, "--------------]\n");
11616     } else {
11617         fprintf(f, "\n");
11618     }
11619
11620     i = backwardMostMove;
11621     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11622
11623     while (i < forwardMostMove) {
11624         if (commentList[i] != NULL) {
11625             fprintf(f, "[%s]\n", commentList[i]);
11626         }
11627
11628         if ((i % 2) == 1) {
11629             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11630             i++;
11631         } else {
11632             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11633             i++;
11634             if (commentList[i] != NULL) {
11635                 fprintf(f, "\n");
11636                 continue;
11637             }
11638             if (i >= forwardMostMove) {
11639                 fprintf(f, "\n");
11640                 break;
11641             }
11642             fprintf(f, "%s\n", parseList[i]);
11643             i++;
11644         }
11645     }
11646
11647     if (commentList[i] != NULL) {
11648         fprintf(f, "[%s]\n", commentList[i]);
11649     }
11650
11651     /* This isn't really the old style, but it's close enough */
11652     if (gameInfo.resultDetails != NULL &&
11653         gameInfo.resultDetails[0] != NULLCHAR) {
11654         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11655                 gameInfo.resultDetails);
11656     } else {
11657         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11658     }
11659
11660     fclose(f);
11661     return TRUE;
11662 }
11663
11664 /* Save the current game to open file f and close the file */
11665 int
11666 SaveGame(f, dummy, dummy2)
11667      FILE *f;
11668      int dummy;
11669      char *dummy2;
11670 {
11671     if (gameMode == EditPosition) EditPositionDone(TRUE);
11672     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11673     if (appData.oldSaveStyle)
11674       return SaveGameOldStyle(f);
11675     else
11676       return SaveGamePGN(f);
11677 }
11678
11679 /* Save the current position to the given file */
11680 int
11681 SavePositionToFile(filename)
11682      char *filename;
11683 {
11684     FILE *f;
11685     char buf[MSG_SIZ];
11686
11687     if (strcmp(filename, "-") == 0) {
11688         return SavePosition(stdout, 0, NULL);
11689     } else {
11690         f = fopen(filename, "a");
11691         if (f == NULL) {
11692             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11693             DisplayError(buf, errno);
11694             return FALSE;
11695         } else {
11696             safeStrCpy(buf, lastMsg, MSG_SIZ);
11697             DisplayMessage(_("Waiting for access to save file"), "");
11698             flock(fileno(f), LOCK_EX); // [HGM] lock
11699             DisplayMessage(_("Saving position"), "");
11700             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11701             SavePosition(f, 0, NULL);
11702             DisplayMessage(buf, "");
11703             return TRUE;
11704         }
11705     }
11706 }
11707
11708 /* Save the current position to the given open file and close the file */
11709 int
11710 SavePosition(f, dummy, dummy2)
11711      FILE *f;
11712      int dummy;
11713      char *dummy2;
11714 {
11715     time_t tm;
11716     char *fen;
11717
11718     if (gameMode == EditPosition) EditPositionDone(TRUE);
11719     if (appData.oldSaveStyle) {
11720         tm = time((time_t *) NULL);
11721
11722         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11723         PrintOpponents(f);
11724         fprintf(f, "[--------------\n");
11725         PrintPosition(f, currentMove);
11726         fprintf(f, "--------------]\n");
11727     } else {
11728         fen = PositionToFEN(currentMove, NULL);
11729         fprintf(f, "%s\n", fen);
11730         free(fen);
11731     }
11732     fclose(f);
11733     return TRUE;
11734 }
11735
11736 void
11737 ReloadCmailMsgEvent(unregister)
11738      int unregister;
11739 {
11740 #if !WIN32
11741     static char *inFilename = NULL;
11742     static char *outFilename;
11743     int i;
11744     struct stat inbuf, outbuf;
11745     int status;
11746
11747     /* Any registered moves are unregistered if unregister is set, */
11748     /* i.e. invoked by the signal handler */
11749     if (unregister) {
11750         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11751             cmailMoveRegistered[i] = FALSE;
11752             if (cmailCommentList[i] != NULL) {
11753                 free(cmailCommentList[i]);
11754                 cmailCommentList[i] = NULL;
11755             }
11756         }
11757         nCmailMovesRegistered = 0;
11758     }
11759
11760     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11761         cmailResult[i] = CMAIL_NOT_RESULT;
11762     }
11763     nCmailResults = 0;
11764
11765     if (inFilename == NULL) {
11766         /* Because the filenames are static they only get malloced once  */
11767         /* and they never get freed                                      */
11768         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11769         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11770
11771         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11772         sprintf(outFilename, "%s.out", appData.cmailGameName);
11773     }
11774
11775     status = stat(outFilename, &outbuf);
11776     if (status < 0) {
11777         cmailMailedMove = FALSE;
11778     } else {
11779         status = stat(inFilename, &inbuf);
11780         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11781     }
11782
11783     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11784        counts the games, notes how each one terminated, etc.
11785
11786        It would be nice to remove this kludge and instead gather all
11787        the information while building the game list.  (And to keep it
11788        in the game list nodes instead of having a bunch of fixed-size
11789        parallel arrays.)  Note this will require getting each game's
11790        termination from the PGN tags, as the game list builder does
11791        not process the game moves.  --mann
11792        */
11793     cmailMsgLoaded = TRUE;
11794     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11795
11796     /* Load first game in the file or popup game menu */
11797     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11798
11799 #endif /* !WIN32 */
11800     return;
11801 }
11802
11803 int
11804 RegisterMove()
11805 {
11806     FILE *f;
11807     char string[MSG_SIZ];
11808
11809     if (   cmailMailedMove
11810         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11811         return TRUE;            /* Allow free viewing  */
11812     }
11813
11814     /* Unregister move to ensure that we don't leave RegisterMove        */
11815     /* with the move registered when the conditions for registering no   */
11816     /* longer hold                                                       */
11817     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11818         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11819         nCmailMovesRegistered --;
11820
11821         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11822           {
11823               free(cmailCommentList[lastLoadGameNumber - 1]);
11824               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11825           }
11826     }
11827
11828     if (cmailOldMove == -1) {
11829         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11830         return FALSE;
11831     }
11832
11833     if (currentMove > cmailOldMove + 1) {
11834         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11835         return FALSE;
11836     }
11837
11838     if (currentMove < cmailOldMove) {
11839         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11840         return FALSE;
11841     }
11842
11843     if (forwardMostMove > currentMove) {
11844         /* Silently truncate extra moves */
11845         TruncateGame();
11846     }
11847
11848     if (   (currentMove == cmailOldMove + 1)
11849         || (   (currentMove == cmailOldMove)
11850             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11851                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11852         if (gameInfo.result != GameUnfinished) {
11853             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11854         }
11855
11856         if (commentList[currentMove] != NULL) {
11857             cmailCommentList[lastLoadGameNumber - 1]
11858               = StrSave(commentList[currentMove]);
11859         }
11860         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11861
11862         if (appData.debugMode)
11863           fprintf(debugFP, "Saving %s for game %d\n",
11864                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11865
11866         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11867
11868         f = fopen(string, "w");
11869         if (appData.oldSaveStyle) {
11870             SaveGameOldStyle(f); /* also closes the file */
11871
11872             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11873             f = fopen(string, "w");
11874             SavePosition(f, 0, NULL); /* also closes the file */
11875         } else {
11876             fprintf(f, "{--------------\n");
11877             PrintPosition(f, currentMove);
11878             fprintf(f, "--------------}\n\n");
11879
11880             SaveGame(f, 0, NULL); /* also closes the file*/
11881         }
11882
11883         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11884         nCmailMovesRegistered ++;
11885     } else if (nCmailGames == 1) {
11886         DisplayError(_("You have not made a move yet"), 0);
11887         return FALSE;
11888     }
11889
11890     return TRUE;
11891 }
11892
11893 void
11894 MailMoveEvent()
11895 {
11896 #if !WIN32
11897     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11898     FILE *commandOutput;
11899     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11900     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11901     int nBuffers;
11902     int i;
11903     int archived;
11904     char *arcDir;
11905
11906     if (! cmailMsgLoaded) {
11907         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11908         return;
11909     }
11910
11911     if (nCmailGames == nCmailResults) {
11912         DisplayError(_("No unfinished games"), 0);
11913         return;
11914     }
11915
11916 #if CMAIL_PROHIBIT_REMAIL
11917     if (cmailMailedMove) {
11918       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);
11919         DisplayError(msg, 0);
11920         return;
11921     }
11922 #endif
11923
11924     if (! (cmailMailedMove || RegisterMove())) return;
11925
11926     if (   cmailMailedMove
11927         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11928       snprintf(string, MSG_SIZ, partCommandString,
11929                appData.debugMode ? " -v" : "", appData.cmailGameName);
11930         commandOutput = popen(string, "r");
11931
11932         if (commandOutput == NULL) {
11933             DisplayError(_("Failed to invoke cmail"), 0);
11934         } else {
11935             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11936                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11937             }
11938             if (nBuffers > 1) {
11939                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11940                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11941                 nBytes = MSG_SIZ - 1;
11942             } else {
11943                 (void) memcpy(msg, buffer, nBytes);
11944             }
11945             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11946
11947             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11948                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11949
11950                 archived = TRUE;
11951                 for (i = 0; i < nCmailGames; i ++) {
11952                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11953                         archived = FALSE;
11954                     }
11955                 }
11956                 if (   archived
11957                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11958                         != NULL)) {
11959                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11960                            arcDir,
11961                            appData.cmailGameName,
11962                            gameInfo.date);
11963                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11964                     cmailMsgLoaded = FALSE;
11965                 }
11966             }
11967
11968             DisplayInformation(msg);
11969             pclose(commandOutput);
11970         }
11971     } else {
11972         if ((*cmailMsg) != '\0') {
11973             DisplayInformation(cmailMsg);
11974         }
11975     }
11976
11977     return;
11978 #endif /* !WIN32 */
11979 }
11980
11981 char *
11982 CmailMsg()
11983 {
11984 #if WIN32
11985     return NULL;
11986 #else
11987     int  prependComma = 0;
11988     char number[5];
11989     char string[MSG_SIZ];       /* Space for game-list */
11990     int  i;
11991
11992     if (!cmailMsgLoaded) return "";
11993
11994     if (cmailMailedMove) {
11995       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11996     } else {
11997         /* Create a list of games left */
11998       snprintf(string, MSG_SIZ, "[");
11999         for (i = 0; i < nCmailGames; i ++) {
12000             if (! (   cmailMoveRegistered[i]
12001                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12002                 if (prependComma) {
12003                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12004                 } else {
12005                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12006                     prependComma = 1;
12007                 }
12008
12009                 strcat(string, number);
12010             }
12011         }
12012         strcat(string, "]");
12013
12014         if (nCmailMovesRegistered + nCmailResults == 0) {
12015             switch (nCmailGames) {
12016               case 1:
12017                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12018                 break;
12019
12020               case 2:
12021                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12022                 break;
12023
12024               default:
12025                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12026                          nCmailGames);
12027                 break;
12028             }
12029         } else {
12030             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12031               case 1:
12032                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12033                          string);
12034                 break;
12035
12036               case 0:
12037                 if (nCmailResults == nCmailGames) {
12038                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12039                 } else {
12040                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12041                 }
12042                 break;
12043
12044               default:
12045                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12046                          string);
12047             }
12048         }
12049     }
12050     return cmailMsg;
12051 #endif /* WIN32 */
12052 }
12053
12054 void
12055 ResetGameEvent()
12056 {
12057     if (gameMode == Training)
12058       SetTrainingModeOff();
12059
12060     Reset(TRUE, TRUE);
12061     cmailMsgLoaded = FALSE;
12062     if (appData.icsActive) {
12063       SendToICS(ics_prefix);
12064       SendToICS("refresh\n");
12065     }
12066 }
12067
12068 void
12069 ExitEvent(status)
12070      int status;
12071 {
12072     exiting++;
12073     if (exiting > 2) {
12074       /* Give up on clean exit */
12075       exit(status);
12076     }
12077     if (exiting > 1) {
12078       /* Keep trying for clean exit */
12079       return;
12080     }
12081
12082     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12083
12084     if (telnetISR != NULL) {
12085       RemoveInputSource(telnetISR);
12086     }
12087     if (icsPR != NoProc) {
12088       DestroyChildProcess(icsPR, TRUE);
12089     }
12090
12091     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12092     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12093
12094     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12095     /* make sure this other one finishes before killing it!                  */
12096     if(endingGame) { int count = 0;
12097         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12098         while(endingGame && count++ < 10) DoSleep(1);
12099         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12100     }
12101
12102     /* Kill off chess programs */
12103     if (first.pr != NoProc) {
12104         ExitAnalyzeMode();
12105
12106         DoSleep( appData.delayBeforeQuit );
12107         SendToProgram("quit\n", &first);
12108         DoSleep( appData.delayAfterQuit );
12109         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12110     }
12111     if (second.pr != NoProc) {
12112         DoSleep( appData.delayBeforeQuit );
12113         SendToProgram("quit\n", &second);
12114         DoSleep( appData.delayAfterQuit );
12115         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12116     }
12117     if (first.isr != NULL) {
12118         RemoveInputSource(first.isr);
12119     }
12120     if (second.isr != NULL) {
12121         RemoveInputSource(second.isr);
12122     }
12123
12124     ShutDownFrontEnd();
12125     exit(status);
12126 }
12127
12128 void
12129 PauseEvent()
12130 {
12131     if (appData.debugMode)
12132         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12133     if (pausing) {
12134         pausing = FALSE;
12135         ModeHighlight();
12136         if (gameMode == MachinePlaysWhite ||
12137             gameMode == MachinePlaysBlack) {
12138             StartClocks();
12139         } else {
12140             DisplayBothClocks();
12141         }
12142         if (gameMode == PlayFromGameFile) {
12143             if (appData.timeDelay >= 0)
12144                 AutoPlayGameLoop();
12145         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12146             Reset(FALSE, TRUE);
12147             SendToICS(ics_prefix);
12148             SendToICS("refresh\n");
12149         } else if (currentMove < forwardMostMove) {
12150             ForwardInner(forwardMostMove);
12151         }
12152         pauseExamInvalid = FALSE;
12153     } else {
12154         switch (gameMode) {
12155           default:
12156             return;
12157           case IcsExamining:
12158             pauseExamForwardMostMove = forwardMostMove;
12159             pauseExamInvalid = FALSE;
12160             /* fall through */
12161           case IcsObserving:
12162           case IcsPlayingWhite:
12163           case IcsPlayingBlack:
12164             pausing = TRUE;
12165             ModeHighlight();
12166             return;
12167           case PlayFromGameFile:
12168             (void) StopLoadGameTimer();
12169             pausing = TRUE;
12170             ModeHighlight();
12171             break;
12172           case BeginningOfGame:
12173             if (appData.icsActive) return;
12174             /* else fall through */
12175           case MachinePlaysWhite:
12176           case MachinePlaysBlack:
12177           case TwoMachinesPlay:
12178             if (forwardMostMove == 0)
12179               return;           /* don't pause if no one has moved */
12180             if ((gameMode == MachinePlaysWhite &&
12181                  !WhiteOnMove(forwardMostMove)) ||
12182                 (gameMode == MachinePlaysBlack &&
12183                  WhiteOnMove(forwardMostMove))) {
12184                 StopClocks();
12185             }
12186             pausing = TRUE;
12187             ModeHighlight();
12188             break;
12189         }
12190     }
12191 }
12192
12193 void
12194 EditCommentEvent()
12195 {
12196     char title[MSG_SIZ];
12197
12198     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12199       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12200     } else {
12201       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12202                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12203                parseList[currentMove - 1]);
12204     }
12205
12206     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12207 }
12208
12209
12210 void
12211 EditTagsEvent()
12212 {
12213     char *tags = PGNTags(&gameInfo);
12214     EditTagsPopUp(tags, NULL);
12215     free(tags);
12216 }
12217
12218 void
12219 AnalyzeModeEvent()
12220 {
12221     if (appData.noChessProgram || gameMode == AnalyzeMode)
12222       return;
12223
12224     if (gameMode != AnalyzeFile) {
12225         if (!appData.icsEngineAnalyze) {
12226                EditGameEvent();
12227                if (gameMode != EditGame) return;
12228         }
12229         ResurrectChessProgram();
12230         SendToProgram("analyze\n", &first);
12231         first.analyzing = TRUE;
12232         /*first.maybeThinking = TRUE;*/
12233         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12234         EngineOutputPopUp();
12235     }
12236     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12237     pausing = FALSE;
12238     ModeHighlight();
12239     SetGameInfo();
12240
12241     StartAnalysisClock();
12242     GetTimeMark(&lastNodeCountTime);
12243     lastNodeCount = 0;
12244 }
12245
12246 void
12247 AnalyzeFileEvent()
12248 {
12249     if (appData.noChessProgram || gameMode == AnalyzeFile)
12250       return;
12251
12252     if (gameMode != AnalyzeMode) {
12253         EditGameEvent();
12254         if (gameMode != EditGame) return;
12255         ResurrectChessProgram();
12256         SendToProgram("analyze\n", &first);
12257         first.analyzing = TRUE;
12258         /*first.maybeThinking = TRUE;*/
12259         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12260         EngineOutputPopUp();
12261     }
12262     gameMode = AnalyzeFile;
12263     pausing = FALSE;
12264     ModeHighlight();
12265     SetGameInfo();
12266
12267     StartAnalysisClock();
12268     GetTimeMark(&lastNodeCountTime);
12269     lastNodeCount = 0;
12270 }
12271
12272 void
12273 MachineWhiteEvent()
12274 {
12275     char buf[MSG_SIZ];
12276     char *bookHit = NULL;
12277
12278     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12279       return;
12280
12281
12282     if (gameMode == PlayFromGameFile ||
12283         gameMode == TwoMachinesPlay  ||
12284         gameMode == Training         ||
12285         gameMode == AnalyzeMode      ||
12286         gameMode == EndOfGame)
12287         EditGameEvent();
12288
12289     if (gameMode == EditPosition)
12290         EditPositionDone(TRUE);
12291
12292     if (!WhiteOnMove(currentMove)) {
12293         DisplayError(_("It is not White's turn"), 0);
12294         return;
12295     }
12296
12297     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12298       ExitAnalyzeMode();
12299
12300     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12301         gameMode == AnalyzeFile)
12302         TruncateGame();
12303
12304     ResurrectChessProgram();    /* in case it isn't running */
12305     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12306         gameMode = MachinePlaysWhite;
12307         ResetClocks();
12308     } else
12309     gameMode = MachinePlaysWhite;
12310     pausing = FALSE;
12311     ModeHighlight();
12312     SetGameInfo();
12313     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12314     DisplayTitle(buf);
12315     if (first.sendName) {
12316       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12317       SendToProgram(buf, &first);
12318     }
12319     if (first.sendTime) {
12320       if (first.useColors) {
12321         SendToProgram("black\n", &first); /*gnu kludge*/
12322       }
12323       SendTimeRemaining(&first, TRUE);
12324     }
12325     if (first.useColors) {
12326       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12327     }
12328     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12329     SetMachineThinkingEnables();
12330     first.maybeThinking = TRUE;
12331     StartClocks();
12332     firstMove = FALSE;
12333
12334     if (appData.autoFlipView && !flipView) {
12335       flipView = !flipView;
12336       DrawPosition(FALSE, NULL);
12337       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12338     }
12339
12340     if(bookHit) { // [HGM] book: simulate book reply
12341         static char bookMove[MSG_SIZ]; // a bit generous?
12342
12343         programStats.nodes = programStats.depth = programStats.time =
12344         programStats.score = programStats.got_only_move = 0;
12345         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12346
12347         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12348         strcat(bookMove, bookHit);
12349         HandleMachineMove(bookMove, &first);
12350     }
12351 }
12352
12353 void
12354 MachineBlackEvent()
12355 {
12356   char buf[MSG_SIZ];
12357   char *bookHit = NULL;
12358
12359     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12360         return;
12361
12362
12363     if (gameMode == PlayFromGameFile ||
12364         gameMode == TwoMachinesPlay  ||
12365         gameMode == Training         ||
12366         gameMode == AnalyzeMode      ||
12367         gameMode == EndOfGame)
12368         EditGameEvent();
12369
12370     if (gameMode == EditPosition)
12371         EditPositionDone(TRUE);
12372
12373     if (WhiteOnMove(currentMove)) {
12374         DisplayError(_("It is not Black's turn"), 0);
12375         return;
12376     }
12377
12378     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12379       ExitAnalyzeMode();
12380
12381     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12382         gameMode == AnalyzeFile)
12383         TruncateGame();
12384
12385     ResurrectChessProgram();    /* in case it isn't running */
12386     gameMode = MachinePlaysBlack;
12387     pausing = FALSE;
12388     ModeHighlight();
12389     SetGameInfo();
12390     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12391     DisplayTitle(buf);
12392     if (first.sendName) {
12393       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12394       SendToProgram(buf, &first);
12395     }
12396     if (first.sendTime) {
12397       if (first.useColors) {
12398         SendToProgram("white\n", &first); /*gnu kludge*/
12399       }
12400       SendTimeRemaining(&first, FALSE);
12401     }
12402     if (first.useColors) {
12403       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12404     }
12405     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12406     SetMachineThinkingEnables();
12407     first.maybeThinking = TRUE;
12408     StartClocks();
12409
12410     if (appData.autoFlipView && flipView) {
12411       flipView = !flipView;
12412       DrawPosition(FALSE, NULL);
12413       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12414     }
12415     if(bookHit) { // [HGM] book: simulate book reply
12416         static char bookMove[MSG_SIZ]; // a bit generous?
12417
12418         programStats.nodes = programStats.depth = programStats.time =
12419         programStats.score = programStats.got_only_move = 0;
12420         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12421
12422         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12423         strcat(bookMove, bookHit);
12424         HandleMachineMove(bookMove, &first);
12425     }
12426 }
12427
12428
12429 void
12430 DisplayTwoMachinesTitle()
12431 {
12432     char buf[MSG_SIZ];
12433     if (appData.matchGames > 0) {
12434         if (first.twoMachinesColor[0] == 'w') {
12435           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12436                    gameInfo.white, gameInfo.black,
12437                    first.matchWins, second.matchWins,
12438                    matchGame - 1 - (first.matchWins + second.matchWins));
12439         } else {
12440           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12441                    gameInfo.white, gameInfo.black,
12442                    second.matchWins, first.matchWins,
12443                    matchGame - 1 - (first.matchWins + second.matchWins));
12444         }
12445     } else {
12446       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12447     }
12448     DisplayTitle(buf);
12449 }
12450
12451 void
12452 SettingsMenuIfReady()
12453 {
12454   if (second.lastPing != second.lastPong) {
12455     DisplayMessage("", _("Waiting for second chess program"));
12456     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12457     return;
12458   }
12459   ThawUI();
12460   DisplayMessage("", "");
12461   SettingsPopUp(&second);
12462 }
12463
12464 int
12465 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12466 {
12467     char buf[MSG_SIZ];
12468     if (cps->pr == NULL) {
12469         StartChessProgram(cps);
12470         if (cps->protocolVersion == 1) {
12471           retry();
12472         } else {
12473           /* kludge: allow timeout for initial "feature" command */
12474           FreezeUI();
12475           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12476           DisplayMessage("", buf);
12477           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12478         }
12479         return 1;
12480     }
12481     return 0;
12482 }
12483
12484 void
12485 TwoMachinesEvent P((void))
12486 {
12487     int i;
12488     char buf[MSG_SIZ];
12489     ChessProgramState *onmove;
12490     char *bookHit = NULL;
12491     static int stalling = 0;
12492     TimeMark now;
12493     long wait;
12494
12495     if (appData.noChessProgram) return;
12496
12497     switch (gameMode) {
12498       case TwoMachinesPlay:
12499         return;
12500       case MachinePlaysWhite:
12501       case MachinePlaysBlack:
12502         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12503             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12504             return;
12505         }
12506         /* fall through */
12507       case BeginningOfGame:
12508       case PlayFromGameFile:
12509       case EndOfGame:
12510         EditGameEvent();
12511         if (gameMode != EditGame) return;
12512         break;
12513       case EditPosition:
12514         EditPositionDone(TRUE);
12515         break;
12516       case AnalyzeMode:
12517       case AnalyzeFile:
12518         ExitAnalyzeMode();
12519         break;
12520       case EditGame:
12521       default:
12522         break;
12523     }
12524
12525 //    forwardMostMove = currentMove;
12526     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12527
12528     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12529
12530     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12531     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12532       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12533       return;
12534     }
12535     if(!stalling) {
12536       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12537       SendToProgram("force\n", &second);
12538       stalling = 1;
12539       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12540       return;
12541     }
12542     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12543     if(appData.matchPause>10000 || appData.matchPause<10)
12544                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12545     wait = SubtractTimeMarks(&now, &pauseStart);
12546     if(wait < appData.matchPause) {
12547         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12548         return;
12549     }
12550     stalling = 0;
12551     DisplayMessage("", "");
12552     if (startedFromSetupPosition) {
12553         SendBoard(&second, backwardMostMove);
12554     if (appData.debugMode) {
12555         fprintf(debugFP, "Two Machines\n");
12556     }
12557     }
12558     for (i = backwardMostMove; i < forwardMostMove; i++) {
12559         SendMoveToProgram(i, &second);
12560     }
12561
12562     gameMode = TwoMachinesPlay;
12563     pausing = FALSE;
12564     ModeHighlight();
12565     SetGameInfo();
12566     DisplayTwoMachinesTitle();
12567     firstMove = TRUE;
12568     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12569         onmove = &first;
12570     } else {
12571         onmove = &second;
12572     }
12573     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12574     SendToProgram(first.computerString, &first);
12575     if (first.sendName) {
12576       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12577       SendToProgram(buf, &first);
12578     }
12579     SendToProgram(second.computerString, &second);
12580     if (second.sendName) {
12581       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12582       SendToProgram(buf, &second);
12583     }
12584
12585     ResetClocks();
12586     if (!first.sendTime || !second.sendTime) {
12587         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12588         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12589     }
12590     if (onmove->sendTime) {
12591       if (onmove->useColors) {
12592         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12593       }
12594       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12595     }
12596     if (onmove->useColors) {
12597       SendToProgram(onmove->twoMachinesColor, onmove);
12598     }
12599     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12600 //    SendToProgram("go\n", onmove);
12601     onmove->maybeThinking = TRUE;
12602     SetMachineThinkingEnables();
12603
12604     StartClocks();
12605
12606     if(bookHit) { // [HGM] book: simulate book reply
12607         static char bookMove[MSG_SIZ]; // a bit generous?
12608
12609         programStats.nodes = programStats.depth = programStats.time =
12610         programStats.score = programStats.got_only_move = 0;
12611         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12612
12613         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12614         strcat(bookMove, bookHit);
12615         savedMessage = bookMove; // args for deferred call
12616         savedState = onmove;
12617         ScheduleDelayedEvent(DeferredBookMove, 1);
12618     }
12619 }
12620
12621 void
12622 TrainingEvent()
12623 {
12624     if (gameMode == Training) {
12625       SetTrainingModeOff();
12626       gameMode = PlayFromGameFile;
12627       DisplayMessage("", _("Training mode off"));
12628     } else {
12629       gameMode = Training;
12630       animateTraining = appData.animate;
12631
12632       /* make sure we are not already at the end of the game */
12633       if (currentMove < forwardMostMove) {
12634         SetTrainingModeOn();
12635         DisplayMessage("", _("Training mode on"));
12636       } else {
12637         gameMode = PlayFromGameFile;
12638         DisplayError(_("Already at end of game"), 0);
12639       }
12640     }
12641     ModeHighlight();
12642 }
12643
12644 void
12645 IcsClientEvent()
12646 {
12647     if (!appData.icsActive) return;
12648     switch (gameMode) {
12649       case IcsPlayingWhite:
12650       case IcsPlayingBlack:
12651       case IcsObserving:
12652       case IcsIdle:
12653       case BeginningOfGame:
12654       case IcsExamining:
12655         return;
12656
12657       case EditGame:
12658         break;
12659
12660       case EditPosition:
12661         EditPositionDone(TRUE);
12662         break;
12663
12664       case AnalyzeMode:
12665       case AnalyzeFile:
12666         ExitAnalyzeMode();
12667         break;
12668
12669       default:
12670         EditGameEvent();
12671         break;
12672     }
12673
12674     gameMode = IcsIdle;
12675     ModeHighlight();
12676     return;
12677 }
12678
12679
12680 void
12681 EditGameEvent()
12682 {
12683     int i;
12684
12685     switch (gameMode) {
12686       case Training:
12687         SetTrainingModeOff();
12688         break;
12689       case MachinePlaysWhite:
12690       case MachinePlaysBlack:
12691       case BeginningOfGame:
12692         SendToProgram("force\n", &first);
12693         SetUserThinkingEnables();
12694         break;
12695       case PlayFromGameFile:
12696         (void) StopLoadGameTimer();
12697         if (gameFileFP != NULL) {
12698             gameFileFP = NULL;
12699         }
12700         break;
12701       case EditPosition:
12702         EditPositionDone(TRUE);
12703         break;
12704       case AnalyzeMode:
12705       case AnalyzeFile:
12706         ExitAnalyzeMode();
12707         SendToProgram("force\n", &first);
12708         break;
12709       case TwoMachinesPlay:
12710         GameEnds(EndOfFile, NULL, GE_PLAYER);
12711         ResurrectChessProgram();
12712         SetUserThinkingEnables();
12713         break;
12714       case EndOfGame:
12715         ResurrectChessProgram();
12716         break;
12717       case IcsPlayingBlack:
12718       case IcsPlayingWhite:
12719         DisplayError(_("Warning: You are still playing a game"), 0);
12720         break;
12721       case IcsObserving:
12722         DisplayError(_("Warning: You are still observing a game"), 0);
12723         break;
12724       case IcsExamining:
12725         DisplayError(_("Warning: You are still examining a game"), 0);
12726         break;
12727       case IcsIdle:
12728         break;
12729       case EditGame:
12730       default:
12731         return;
12732     }
12733
12734     pausing = FALSE;
12735     StopClocks();
12736     first.offeredDraw = second.offeredDraw = 0;
12737
12738     if (gameMode == PlayFromGameFile) {
12739         whiteTimeRemaining = timeRemaining[0][currentMove];
12740         blackTimeRemaining = timeRemaining[1][currentMove];
12741         DisplayTitle("");
12742     }
12743
12744     if (gameMode == MachinePlaysWhite ||
12745         gameMode == MachinePlaysBlack ||
12746         gameMode == TwoMachinesPlay ||
12747         gameMode == EndOfGame) {
12748         i = forwardMostMove;
12749         while (i > currentMove) {
12750             SendToProgram("undo\n", &first);
12751             i--;
12752         }
12753         whiteTimeRemaining = timeRemaining[0][currentMove];
12754         blackTimeRemaining = timeRemaining[1][currentMove];
12755         DisplayBothClocks();
12756         if (whiteFlag || blackFlag) {
12757             whiteFlag = blackFlag = 0;
12758         }
12759         DisplayTitle("");
12760     }
12761
12762     gameMode = EditGame;
12763     ModeHighlight();
12764     SetGameInfo();
12765 }
12766
12767
12768 void
12769 EditPositionEvent()
12770 {
12771     if (gameMode == EditPosition) {
12772         EditGameEvent();
12773         return;
12774     }
12775
12776     EditGameEvent();
12777     if (gameMode != EditGame) return;
12778
12779     gameMode = EditPosition;
12780     ModeHighlight();
12781     SetGameInfo();
12782     if (currentMove > 0)
12783       CopyBoard(boards[0], boards[currentMove]);
12784
12785     blackPlaysFirst = !WhiteOnMove(currentMove);
12786     ResetClocks();
12787     currentMove = forwardMostMove = backwardMostMove = 0;
12788     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12789     DisplayMove(-1);
12790 }
12791
12792 void
12793 ExitAnalyzeMode()
12794 {
12795     /* [DM] icsEngineAnalyze - possible call from other functions */
12796     if (appData.icsEngineAnalyze) {
12797         appData.icsEngineAnalyze = FALSE;
12798
12799         DisplayMessage("",_("Close ICS engine analyze..."));
12800     }
12801     if (first.analysisSupport && first.analyzing) {
12802       SendToProgram("exit\n", &first);
12803       first.analyzing = FALSE;
12804     }
12805     thinkOutput[0] = NULLCHAR;
12806 }
12807
12808 void
12809 EditPositionDone(Boolean fakeRights)
12810 {
12811     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12812
12813     startedFromSetupPosition = TRUE;
12814     InitChessProgram(&first, FALSE);
12815     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12816       boards[0][EP_STATUS] = EP_NONE;
12817       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12818     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12819         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12820         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12821       } else boards[0][CASTLING][2] = NoRights;
12822     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12823         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12824         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12825       } else boards[0][CASTLING][5] = NoRights;
12826     }
12827     SendToProgram("force\n", &first);
12828     if (blackPlaysFirst) {
12829         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12830         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12831         currentMove = forwardMostMove = backwardMostMove = 1;
12832         CopyBoard(boards[1], boards[0]);
12833     } else {
12834         currentMove = forwardMostMove = backwardMostMove = 0;
12835     }
12836     SendBoard(&first, forwardMostMove);
12837     if (appData.debugMode) {
12838         fprintf(debugFP, "EditPosDone\n");
12839     }
12840     DisplayTitle("");
12841     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12842     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12843     gameMode = EditGame;
12844     ModeHighlight();
12845     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12846     ClearHighlights(); /* [AS] */
12847 }
12848
12849 /* Pause for `ms' milliseconds */
12850 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12851 void
12852 TimeDelay(ms)
12853      long ms;
12854 {
12855     TimeMark m1, m2;
12856
12857     GetTimeMark(&m1);
12858     do {
12859         GetTimeMark(&m2);
12860     } while (SubtractTimeMarks(&m2, &m1) < ms);
12861 }
12862
12863 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12864 void
12865 SendMultiLineToICS(buf)
12866      char *buf;
12867 {
12868     char temp[MSG_SIZ+1], *p;
12869     int len;
12870
12871     len = strlen(buf);
12872     if (len > MSG_SIZ)
12873       len = MSG_SIZ;
12874
12875     strncpy(temp, buf, len);
12876     temp[len] = 0;
12877
12878     p = temp;
12879     while (*p) {
12880         if (*p == '\n' || *p == '\r')
12881           *p = ' ';
12882         ++p;
12883     }
12884
12885     strcat(temp, "\n");
12886     SendToICS(temp);
12887     SendToPlayer(temp, strlen(temp));
12888 }
12889
12890 void
12891 SetWhiteToPlayEvent()
12892 {
12893     if (gameMode == EditPosition) {
12894         blackPlaysFirst = FALSE;
12895         DisplayBothClocks();    /* works because currentMove is 0 */
12896     } else if (gameMode == IcsExamining) {
12897         SendToICS(ics_prefix);
12898         SendToICS("tomove white\n");
12899     }
12900 }
12901
12902 void
12903 SetBlackToPlayEvent()
12904 {
12905     if (gameMode == EditPosition) {
12906         blackPlaysFirst = TRUE;
12907         currentMove = 1;        /* kludge */
12908         DisplayBothClocks();
12909         currentMove = 0;
12910     } else if (gameMode == IcsExamining) {
12911         SendToICS(ics_prefix);
12912         SendToICS("tomove black\n");
12913     }
12914 }
12915
12916 void
12917 EditPositionMenuEvent(selection, x, y)
12918      ChessSquare selection;
12919      int x, y;
12920 {
12921     char buf[MSG_SIZ];
12922     ChessSquare piece = boards[0][y][x];
12923
12924     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12925
12926     switch (selection) {
12927       case ClearBoard:
12928         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12929             SendToICS(ics_prefix);
12930             SendToICS("bsetup clear\n");
12931         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12932             SendToICS(ics_prefix);
12933             SendToICS("clearboard\n");
12934         } else {
12935             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12936                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12937                 for (y = 0; y < BOARD_HEIGHT; y++) {
12938                     if (gameMode == IcsExamining) {
12939                         if (boards[currentMove][y][x] != EmptySquare) {
12940                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12941                                     AAA + x, ONE + y);
12942                             SendToICS(buf);
12943                         }
12944                     } else {
12945                         boards[0][y][x] = p;
12946                     }
12947                 }
12948             }
12949         }
12950         if (gameMode == EditPosition) {
12951             DrawPosition(FALSE, boards[0]);
12952         }
12953         break;
12954
12955       case WhitePlay:
12956         SetWhiteToPlayEvent();
12957         break;
12958
12959       case BlackPlay:
12960         SetBlackToPlayEvent();
12961         break;
12962
12963       case EmptySquare:
12964         if (gameMode == IcsExamining) {
12965             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12966             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12967             SendToICS(buf);
12968         } else {
12969             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12970                 if(x == BOARD_LEFT-2) {
12971                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12972                     boards[0][y][1] = 0;
12973                 } else
12974                 if(x == BOARD_RGHT+1) {
12975                     if(y >= gameInfo.holdingsSize) break;
12976                     boards[0][y][BOARD_WIDTH-2] = 0;
12977                 } else break;
12978             }
12979             boards[0][y][x] = EmptySquare;
12980             DrawPosition(FALSE, boards[0]);
12981         }
12982         break;
12983
12984       case PromotePiece:
12985         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12986            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12987             selection = (ChessSquare) (PROMOTED piece);
12988         } else if(piece == EmptySquare) selection = WhiteSilver;
12989         else selection = (ChessSquare)((int)piece - 1);
12990         goto defaultlabel;
12991
12992       case DemotePiece:
12993         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12994            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12995             selection = (ChessSquare) (DEMOTED piece);
12996         } else if(piece == EmptySquare) selection = BlackSilver;
12997         else selection = (ChessSquare)((int)piece + 1);
12998         goto defaultlabel;
12999
13000       case WhiteQueen:
13001       case BlackQueen:
13002         if(gameInfo.variant == VariantShatranj ||
13003            gameInfo.variant == VariantXiangqi  ||
13004            gameInfo.variant == VariantCourier  ||
13005            gameInfo.variant == VariantMakruk     )
13006             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13007         goto defaultlabel;
13008
13009       case WhiteKing:
13010       case BlackKing:
13011         if(gameInfo.variant == VariantXiangqi)
13012             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13013         if(gameInfo.variant == VariantKnightmate)
13014             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13015       default:
13016         defaultlabel:
13017         if (gameMode == IcsExamining) {
13018             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13019             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13020                      PieceToChar(selection), AAA + x, ONE + y);
13021             SendToICS(buf);
13022         } else {
13023             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13024                 int n;
13025                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13026                     n = PieceToNumber(selection - BlackPawn);
13027                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13028                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13029                     boards[0][BOARD_HEIGHT-1-n][1]++;
13030                 } else
13031                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13032                     n = PieceToNumber(selection);
13033                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13034                     boards[0][n][BOARD_WIDTH-1] = selection;
13035                     boards[0][n][BOARD_WIDTH-2]++;
13036                 }
13037             } else
13038             boards[0][y][x] = selection;
13039             DrawPosition(TRUE, boards[0]);
13040         }
13041         break;
13042     }
13043 }
13044
13045
13046 void
13047 DropMenuEvent(selection, x, y)
13048      ChessSquare selection;
13049      int x, y;
13050 {
13051     ChessMove moveType;
13052
13053     switch (gameMode) {
13054       case IcsPlayingWhite:
13055       case MachinePlaysBlack:
13056         if (!WhiteOnMove(currentMove)) {
13057             DisplayMoveError(_("It is Black's turn"));
13058             return;
13059         }
13060         moveType = WhiteDrop;
13061         break;
13062       case IcsPlayingBlack:
13063       case MachinePlaysWhite:
13064         if (WhiteOnMove(currentMove)) {
13065             DisplayMoveError(_("It is White's turn"));
13066             return;
13067         }
13068         moveType = BlackDrop;
13069         break;
13070       case EditGame:
13071         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13072         break;
13073       default:
13074         return;
13075     }
13076
13077     if (moveType == BlackDrop && selection < BlackPawn) {
13078       selection = (ChessSquare) ((int) selection
13079                                  + (int) BlackPawn - (int) WhitePawn);
13080     }
13081     if (boards[currentMove][y][x] != EmptySquare) {
13082         DisplayMoveError(_("That square is occupied"));
13083         return;
13084     }
13085
13086     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13087 }
13088
13089 void
13090 AcceptEvent()
13091 {
13092     /* Accept a pending offer of any kind from opponent */
13093
13094     if (appData.icsActive) {
13095         SendToICS(ics_prefix);
13096         SendToICS("accept\n");
13097     } else if (cmailMsgLoaded) {
13098         if (currentMove == cmailOldMove &&
13099             commentList[cmailOldMove] != NULL &&
13100             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13101                    "Black offers a draw" : "White offers a draw")) {
13102             TruncateGame();
13103             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13104             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13105         } else {
13106             DisplayError(_("There is no pending offer on this move"), 0);
13107             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13108         }
13109     } else {
13110         /* Not used for offers from chess program */
13111     }
13112 }
13113
13114 void
13115 DeclineEvent()
13116 {
13117     /* Decline a pending offer of any kind from opponent */
13118
13119     if (appData.icsActive) {
13120         SendToICS(ics_prefix);
13121         SendToICS("decline\n");
13122     } else if (cmailMsgLoaded) {
13123         if (currentMove == cmailOldMove &&
13124             commentList[cmailOldMove] != NULL &&
13125             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13126                    "Black offers a draw" : "White offers a draw")) {
13127 #ifdef NOTDEF
13128             AppendComment(cmailOldMove, "Draw declined", TRUE);
13129             DisplayComment(cmailOldMove - 1, "Draw declined");
13130 #endif /*NOTDEF*/
13131         } else {
13132             DisplayError(_("There is no pending offer on this move"), 0);
13133         }
13134     } else {
13135         /* Not used for offers from chess program */
13136     }
13137 }
13138
13139 void
13140 RematchEvent()
13141 {
13142     /* Issue ICS rematch command */
13143     if (appData.icsActive) {
13144         SendToICS(ics_prefix);
13145         SendToICS("rematch\n");
13146     }
13147 }
13148
13149 void
13150 CallFlagEvent()
13151 {
13152     /* Call your opponent's flag (claim a win on time) */
13153     if (appData.icsActive) {
13154         SendToICS(ics_prefix);
13155         SendToICS("flag\n");
13156     } else {
13157         switch (gameMode) {
13158           default:
13159             return;
13160           case MachinePlaysWhite:
13161             if (whiteFlag) {
13162                 if (blackFlag)
13163                   GameEnds(GameIsDrawn, "Both players ran out of time",
13164                            GE_PLAYER);
13165                 else
13166                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13167             } else {
13168                 DisplayError(_("Your opponent is not out of time"), 0);
13169             }
13170             break;
13171           case MachinePlaysBlack:
13172             if (blackFlag) {
13173                 if (whiteFlag)
13174                   GameEnds(GameIsDrawn, "Both players ran out of time",
13175                            GE_PLAYER);
13176                 else
13177                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13178             } else {
13179                 DisplayError(_("Your opponent is not out of time"), 0);
13180             }
13181             break;
13182         }
13183     }
13184 }
13185
13186 void
13187 ClockClick(int which)
13188 {       // [HGM] code moved to back-end from winboard.c
13189         if(which) { // black clock
13190           if (gameMode == EditPosition || gameMode == IcsExamining) {
13191             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13192             SetBlackToPlayEvent();
13193           } else if (gameMode == EditGame || shiftKey) {
13194             AdjustClock(which, -1);
13195           } else if (gameMode == IcsPlayingWhite ||
13196                      gameMode == MachinePlaysBlack) {
13197             CallFlagEvent();
13198           }
13199         } else { // white clock
13200           if (gameMode == EditPosition || gameMode == IcsExamining) {
13201             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13202             SetWhiteToPlayEvent();
13203           } else if (gameMode == EditGame || shiftKey) {
13204             AdjustClock(which, -1);
13205           } else if (gameMode == IcsPlayingBlack ||
13206                    gameMode == MachinePlaysWhite) {
13207             CallFlagEvent();
13208           }
13209         }
13210 }
13211
13212 void
13213 DrawEvent()
13214 {
13215     /* Offer draw or accept pending draw offer from opponent */
13216
13217     if (appData.icsActive) {
13218         /* Note: tournament rules require draw offers to be
13219            made after you make your move but before you punch
13220            your clock.  Currently ICS doesn't let you do that;
13221            instead, you immediately punch your clock after making
13222            a move, but you can offer a draw at any time. */
13223
13224         SendToICS(ics_prefix);
13225         SendToICS("draw\n");
13226         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13227     } else if (cmailMsgLoaded) {
13228         if (currentMove == cmailOldMove &&
13229             commentList[cmailOldMove] != NULL &&
13230             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13231                    "Black offers a draw" : "White offers a draw")) {
13232             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13233             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13234         } else if (currentMove == cmailOldMove + 1) {
13235             char *offer = WhiteOnMove(cmailOldMove) ?
13236               "White offers a draw" : "Black offers a draw";
13237             AppendComment(currentMove, offer, TRUE);
13238             DisplayComment(currentMove - 1, offer);
13239             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13240         } else {
13241             DisplayError(_("You must make your move before offering a draw"), 0);
13242             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13243         }
13244     } else if (first.offeredDraw) {
13245         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13246     } else {
13247         if (first.sendDrawOffers) {
13248             SendToProgram("draw\n", &first);
13249             userOfferedDraw = TRUE;
13250         }
13251     }
13252 }
13253
13254 void
13255 AdjournEvent()
13256 {
13257     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13258
13259     if (appData.icsActive) {
13260         SendToICS(ics_prefix);
13261         SendToICS("adjourn\n");
13262     } else {
13263         /* Currently GNU Chess doesn't offer or accept Adjourns */
13264     }
13265 }
13266
13267
13268 void
13269 AbortEvent()
13270 {
13271     /* Offer Abort or accept pending Abort offer from opponent */
13272
13273     if (appData.icsActive) {
13274         SendToICS(ics_prefix);
13275         SendToICS("abort\n");
13276     } else {
13277         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13278     }
13279 }
13280
13281 void
13282 ResignEvent()
13283 {
13284     /* Resign.  You can do this even if it's not your turn. */
13285
13286     if (appData.icsActive) {
13287         SendToICS(ics_prefix);
13288         SendToICS("resign\n");
13289     } else {
13290         switch (gameMode) {
13291           case MachinePlaysWhite:
13292             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13293             break;
13294           case MachinePlaysBlack:
13295             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13296             break;
13297           case EditGame:
13298             if (cmailMsgLoaded) {
13299                 TruncateGame();
13300                 if (WhiteOnMove(cmailOldMove)) {
13301                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13302                 } else {
13303                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13304                 }
13305                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13306             }
13307             break;
13308           default:
13309             break;
13310         }
13311     }
13312 }
13313
13314
13315 void
13316 StopObservingEvent()
13317 {
13318     /* Stop observing current games */
13319     SendToICS(ics_prefix);
13320     SendToICS("unobserve\n");
13321 }
13322
13323 void
13324 StopExaminingEvent()
13325 {
13326     /* Stop observing current game */
13327     SendToICS(ics_prefix);
13328     SendToICS("unexamine\n");
13329 }
13330
13331 void
13332 ForwardInner(target)
13333      int target;
13334 {
13335     int limit;
13336
13337     if (appData.debugMode)
13338         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13339                 target, currentMove, forwardMostMove);
13340
13341     if (gameMode == EditPosition)
13342       return;
13343
13344     if (gameMode == PlayFromGameFile && !pausing)
13345       PauseEvent();
13346
13347     if (gameMode == IcsExamining && pausing)
13348       limit = pauseExamForwardMostMove;
13349     else
13350       limit = forwardMostMove;
13351
13352     if (target > limit) target = limit;
13353
13354     if (target > 0 && moveList[target - 1][0]) {
13355         int fromX, fromY, toX, toY;
13356         toX = moveList[target - 1][2] - AAA;
13357         toY = moveList[target - 1][3] - ONE;
13358         if (moveList[target - 1][1] == '@') {
13359             if (appData.highlightLastMove) {
13360                 SetHighlights(-1, -1, toX, toY);
13361             }
13362         } else {
13363             fromX = moveList[target - 1][0] - AAA;
13364             fromY = moveList[target - 1][1] - ONE;
13365             if (target == currentMove + 1) {
13366                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13367             }
13368             if (appData.highlightLastMove) {
13369                 SetHighlights(fromX, fromY, toX, toY);
13370             }
13371         }
13372     }
13373     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13374         gameMode == Training || gameMode == PlayFromGameFile ||
13375         gameMode == AnalyzeFile) {
13376         while (currentMove < target) {
13377             SendMoveToProgram(currentMove++, &first);
13378         }
13379     } else {
13380         currentMove = target;
13381     }
13382
13383     if (gameMode == EditGame || gameMode == EndOfGame) {
13384         whiteTimeRemaining = timeRemaining[0][currentMove];
13385         blackTimeRemaining = timeRemaining[1][currentMove];
13386     }
13387     DisplayBothClocks();
13388     DisplayMove(currentMove - 1);
13389     DrawPosition(FALSE, boards[currentMove]);
13390     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13391     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13392         DisplayComment(currentMove - 1, commentList[currentMove]);
13393     }
13394 }
13395
13396
13397 void
13398 ForwardEvent()
13399 {
13400     if (gameMode == IcsExamining && !pausing) {
13401         SendToICS(ics_prefix);
13402         SendToICS("forward\n");
13403     } else {
13404         ForwardInner(currentMove + 1);
13405     }
13406 }
13407
13408 void
13409 ToEndEvent()
13410 {
13411     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13412         /* to optimze, we temporarily turn off analysis mode while we feed
13413          * the remaining moves to the engine. Otherwise we get analysis output
13414          * after each move.
13415          */
13416         if (first.analysisSupport) {
13417           SendToProgram("exit\nforce\n", &first);
13418           first.analyzing = FALSE;
13419         }
13420     }
13421
13422     if (gameMode == IcsExamining && !pausing) {
13423         SendToICS(ics_prefix);
13424         SendToICS("forward 999999\n");
13425     } else {
13426         ForwardInner(forwardMostMove);
13427     }
13428
13429     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13430         /* we have fed all the moves, so reactivate analysis mode */
13431         SendToProgram("analyze\n", &first);
13432         first.analyzing = TRUE;
13433         /*first.maybeThinking = TRUE;*/
13434         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13435     }
13436 }
13437
13438 void
13439 BackwardInner(target)
13440      int target;
13441 {
13442     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13443
13444     if (appData.debugMode)
13445         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13446                 target, currentMove, forwardMostMove);
13447
13448     if (gameMode == EditPosition) return;
13449     if (currentMove <= backwardMostMove) {
13450         ClearHighlights();
13451         DrawPosition(full_redraw, boards[currentMove]);
13452         return;
13453     }
13454     if (gameMode == PlayFromGameFile && !pausing)
13455       PauseEvent();
13456
13457     if (moveList[target][0]) {
13458         int fromX, fromY, toX, toY;
13459         toX = moveList[target][2] - AAA;
13460         toY = moveList[target][3] - ONE;
13461         if (moveList[target][1] == '@') {
13462             if (appData.highlightLastMove) {
13463                 SetHighlights(-1, -1, toX, toY);
13464             }
13465         } else {
13466             fromX = moveList[target][0] - AAA;
13467             fromY = moveList[target][1] - ONE;
13468             if (target == currentMove - 1) {
13469                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13470             }
13471             if (appData.highlightLastMove) {
13472                 SetHighlights(fromX, fromY, toX, toY);
13473             }
13474         }
13475     }
13476     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13477         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13478         while (currentMove > target) {
13479             SendToProgram("undo\n", &first);
13480             currentMove--;
13481         }
13482     } else {
13483         currentMove = target;
13484     }
13485
13486     if (gameMode == EditGame || gameMode == EndOfGame) {
13487         whiteTimeRemaining = timeRemaining[0][currentMove];
13488         blackTimeRemaining = timeRemaining[1][currentMove];
13489     }
13490     DisplayBothClocks();
13491     DisplayMove(currentMove - 1);
13492     DrawPosition(full_redraw, boards[currentMove]);
13493     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13494     // [HGM] PV info: routine tests if comment empty
13495     DisplayComment(currentMove - 1, commentList[currentMove]);
13496 }
13497
13498 void
13499 BackwardEvent()
13500 {
13501     if (gameMode == IcsExamining && !pausing) {
13502         SendToICS(ics_prefix);
13503         SendToICS("backward\n");
13504     } else {
13505         BackwardInner(currentMove - 1);
13506     }
13507 }
13508
13509 void
13510 ToStartEvent()
13511 {
13512     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13513         /* to optimize, we temporarily turn off analysis mode while we undo
13514          * all the moves. Otherwise we get analysis output after each undo.
13515          */
13516         if (first.analysisSupport) {
13517           SendToProgram("exit\nforce\n", &first);
13518           first.analyzing = FALSE;
13519         }
13520     }
13521
13522     if (gameMode == IcsExamining && !pausing) {
13523         SendToICS(ics_prefix);
13524         SendToICS("backward 999999\n");
13525     } else {
13526         BackwardInner(backwardMostMove);
13527     }
13528
13529     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13530         /* we have fed all the moves, so reactivate analysis mode */
13531         SendToProgram("analyze\n", &first);
13532         first.analyzing = TRUE;
13533         /*first.maybeThinking = TRUE;*/
13534         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13535     }
13536 }
13537
13538 void
13539 ToNrEvent(int to)
13540 {
13541   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13542   if (to >= forwardMostMove) to = forwardMostMove;
13543   if (to <= backwardMostMove) to = backwardMostMove;
13544   if (to < currentMove) {
13545     BackwardInner(to);
13546   } else {
13547     ForwardInner(to);
13548   }
13549 }
13550
13551 void
13552 RevertEvent(Boolean annotate)
13553 {
13554     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13555         return;
13556     }
13557     if (gameMode != IcsExamining) {
13558         DisplayError(_("You are not examining a game"), 0);
13559         return;
13560     }
13561     if (pausing) {
13562         DisplayError(_("You can't revert while pausing"), 0);
13563         return;
13564     }
13565     SendToICS(ics_prefix);
13566     SendToICS("revert\n");
13567 }
13568
13569 void
13570 RetractMoveEvent()
13571 {
13572     switch (gameMode) {
13573       case MachinePlaysWhite:
13574       case MachinePlaysBlack:
13575         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13576             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13577             return;
13578         }
13579         if (forwardMostMove < 2) return;
13580         currentMove = forwardMostMove = forwardMostMove - 2;
13581         whiteTimeRemaining = timeRemaining[0][currentMove];
13582         blackTimeRemaining = timeRemaining[1][currentMove];
13583         DisplayBothClocks();
13584         DisplayMove(currentMove - 1);
13585         ClearHighlights();/*!! could figure this out*/
13586         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13587         SendToProgram("remove\n", &first);
13588         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13589         break;
13590
13591       case BeginningOfGame:
13592       default:
13593         break;
13594
13595       case IcsPlayingWhite:
13596       case IcsPlayingBlack:
13597         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13598             SendToICS(ics_prefix);
13599             SendToICS("takeback 2\n");
13600         } else {
13601             SendToICS(ics_prefix);
13602             SendToICS("takeback 1\n");
13603         }
13604         break;
13605     }
13606 }
13607
13608 void
13609 MoveNowEvent()
13610 {
13611     ChessProgramState *cps;
13612
13613     switch (gameMode) {
13614       case MachinePlaysWhite:
13615         if (!WhiteOnMove(forwardMostMove)) {
13616             DisplayError(_("It is your turn"), 0);
13617             return;
13618         }
13619         cps = &first;
13620         break;
13621       case MachinePlaysBlack:
13622         if (WhiteOnMove(forwardMostMove)) {
13623             DisplayError(_("It is your turn"), 0);
13624             return;
13625         }
13626         cps = &first;
13627         break;
13628       case TwoMachinesPlay:
13629         if (WhiteOnMove(forwardMostMove) ==
13630             (first.twoMachinesColor[0] == 'w')) {
13631             cps = &first;
13632         } else {
13633             cps = &second;
13634         }
13635         break;
13636       case BeginningOfGame:
13637       default:
13638         return;
13639     }
13640     SendToProgram("?\n", cps);
13641 }
13642
13643 void
13644 TruncateGameEvent()
13645 {
13646     EditGameEvent();
13647     if (gameMode != EditGame) return;
13648     TruncateGame();
13649 }
13650
13651 void
13652 TruncateGame()
13653 {
13654     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13655     if (forwardMostMove > currentMove) {
13656         if (gameInfo.resultDetails != NULL) {
13657             free(gameInfo.resultDetails);
13658             gameInfo.resultDetails = NULL;
13659             gameInfo.result = GameUnfinished;
13660         }
13661         forwardMostMove = currentMove;
13662         HistorySet(parseList, backwardMostMove, forwardMostMove,
13663                    currentMove-1);
13664     }
13665 }
13666
13667 void
13668 HintEvent()
13669 {
13670     if (appData.noChessProgram) return;
13671     switch (gameMode) {
13672       case MachinePlaysWhite:
13673         if (WhiteOnMove(forwardMostMove)) {
13674             DisplayError(_("Wait until your turn"), 0);
13675             return;
13676         }
13677         break;
13678       case BeginningOfGame:
13679       case MachinePlaysBlack:
13680         if (!WhiteOnMove(forwardMostMove)) {
13681             DisplayError(_("Wait until your turn"), 0);
13682             return;
13683         }
13684         break;
13685       default:
13686         DisplayError(_("No hint available"), 0);
13687         return;
13688     }
13689     SendToProgram("hint\n", &first);
13690     hintRequested = TRUE;
13691 }
13692
13693 void
13694 BookEvent()
13695 {
13696     if (appData.noChessProgram) return;
13697     switch (gameMode) {
13698       case MachinePlaysWhite:
13699         if (WhiteOnMove(forwardMostMove)) {
13700             DisplayError(_("Wait until your turn"), 0);
13701             return;
13702         }
13703         break;
13704       case BeginningOfGame:
13705       case MachinePlaysBlack:
13706         if (!WhiteOnMove(forwardMostMove)) {
13707             DisplayError(_("Wait until your turn"), 0);
13708             return;
13709         }
13710         break;
13711       case EditPosition:
13712         EditPositionDone(TRUE);
13713         break;
13714       case TwoMachinesPlay:
13715         return;
13716       default:
13717         break;
13718     }
13719     SendToProgram("bk\n", &first);
13720     bookOutput[0] = NULLCHAR;
13721     bookRequested = TRUE;
13722 }
13723
13724 void
13725 AboutGameEvent()
13726 {
13727     char *tags = PGNTags(&gameInfo);
13728     TagsPopUp(tags, CmailMsg());
13729     free(tags);
13730 }
13731
13732 /* end button procedures */
13733
13734 void
13735 PrintPosition(fp, move)
13736      FILE *fp;
13737      int move;
13738 {
13739     int i, j;
13740
13741     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13742         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13743             char c = PieceToChar(boards[move][i][j]);
13744             fputc(c == 'x' ? '.' : c, fp);
13745             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13746         }
13747     }
13748     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13749       fprintf(fp, "white to play\n");
13750     else
13751       fprintf(fp, "black to play\n");
13752 }
13753
13754 void
13755 PrintOpponents(fp)
13756      FILE *fp;
13757 {
13758     if (gameInfo.white != NULL) {
13759         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13760     } else {
13761         fprintf(fp, "\n");
13762     }
13763 }
13764
13765 /* Find last component of program's own name, using some heuristics */
13766 void
13767 TidyProgramName(prog, host, buf)
13768      char *prog, *host, buf[MSG_SIZ];
13769 {
13770     char *p, *q;
13771     int local = (strcmp(host, "localhost") == 0);
13772     while (!local && (p = strchr(prog, ';')) != NULL) {
13773         p++;
13774         while (*p == ' ') p++;
13775         prog = p;
13776     }
13777     if (*prog == '"' || *prog == '\'') {
13778         q = strchr(prog + 1, *prog);
13779     } else {
13780         q = strchr(prog, ' ');
13781     }
13782     if (q == NULL) q = prog + strlen(prog);
13783     p = q;
13784     while (p >= prog && *p != '/' && *p != '\\') p--;
13785     p++;
13786     if(p == prog && *p == '"') p++;
13787     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13788     memcpy(buf, p, q - p);
13789     buf[q - p] = NULLCHAR;
13790     if (!local) {
13791         strcat(buf, "@");
13792         strcat(buf, host);
13793     }
13794 }
13795
13796 char *
13797 TimeControlTagValue()
13798 {
13799     char buf[MSG_SIZ];
13800     if (!appData.clockMode) {
13801       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13802     } else if (movesPerSession > 0) {
13803       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13804     } else if (timeIncrement == 0) {
13805       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13806     } else {
13807       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13808     }
13809     return StrSave(buf);
13810 }
13811
13812 void
13813 SetGameInfo()
13814 {
13815     /* This routine is used only for certain modes */
13816     VariantClass v = gameInfo.variant;
13817     ChessMove r = GameUnfinished;
13818     char *p = NULL;
13819
13820     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13821         r = gameInfo.result;
13822         p = gameInfo.resultDetails;
13823         gameInfo.resultDetails = NULL;
13824     }
13825     ClearGameInfo(&gameInfo);
13826     gameInfo.variant = v;
13827
13828     switch (gameMode) {
13829       case MachinePlaysWhite:
13830         gameInfo.event = StrSave( appData.pgnEventHeader );
13831         gameInfo.site = StrSave(HostName());
13832         gameInfo.date = PGNDate();
13833         gameInfo.round = StrSave("-");
13834         gameInfo.white = StrSave(first.tidy);
13835         gameInfo.black = StrSave(UserName());
13836         gameInfo.timeControl = TimeControlTagValue();
13837         break;
13838
13839       case MachinePlaysBlack:
13840         gameInfo.event = StrSave( appData.pgnEventHeader );
13841         gameInfo.site = StrSave(HostName());
13842         gameInfo.date = PGNDate();
13843         gameInfo.round = StrSave("-");
13844         gameInfo.white = StrSave(UserName());
13845         gameInfo.black = StrSave(first.tidy);
13846         gameInfo.timeControl = TimeControlTagValue();
13847         break;
13848
13849       case TwoMachinesPlay:
13850         gameInfo.event = StrSave( appData.pgnEventHeader );
13851         gameInfo.site = StrSave(HostName());
13852         gameInfo.date = PGNDate();
13853         if (roundNr > 0) {
13854             char buf[MSG_SIZ];
13855             snprintf(buf, MSG_SIZ, "%d", roundNr);
13856             gameInfo.round = StrSave(buf);
13857         } else {
13858             gameInfo.round = StrSave("-");
13859         }
13860         if (first.twoMachinesColor[0] == 'w') {
13861             gameInfo.white = StrSave(first.tidy);
13862             gameInfo.black = StrSave(second.tidy);
13863         } else {
13864             gameInfo.white = StrSave(second.tidy);
13865             gameInfo.black = StrSave(first.tidy);
13866         }
13867         gameInfo.timeControl = TimeControlTagValue();
13868         break;
13869
13870       case EditGame:
13871         gameInfo.event = StrSave("Edited game");
13872         gameInfo.site = StrSave(HostName());
13873         gameInfo.date = PGNDate();
13874         gameInfo.round = StrSave("-");
13875         gameInfo.white = StrSave("-");
13876         gameInfo.black = StrSave("-");
13877         gameInfo.result = r;
13878         gameInfo.resultDetails = p;
13879         break;
13880
13881       case EditPosition:
13882         gameInfo.event = StrSave("Edited position");
13883         gameInfo.site = StrSave(HostName());
13884         gameInfo.date = PGNDate();
13885         gameInfo.round = StrSave("-");
13886         gameInfo.white = StrSave("-");
13887         gameInfo.black = StrSave("-");
13888         break;
13889
13890       case IcsPlayingWhite:
13891       case IcsPlayingBlack:
13892       case IcsObserving:
13893       case IcsExamining:
13894         break;
13895
13896       case PlayFromGameFile:
13897         gameInfo.event = StrSave("Game from non-PGN file");
13898         gameInfo.site = StrSave(HostName());
13899         gameInfo.date = PGNDate();
13900         gameInfo.round = StrSave("-");
13901         gameInfo.white = StrSave("?");
13902         gameInfo.black = StrSave("?");
13903         break;
13904
13905       default:
13906         break;
13907     }
13908 }
13909
13910 void
13911 ReplaceComment(index, text)
13912      int index;
13913      char *text;
13914 {
13915     int len;
13916     char *p;
13917     float score;
13918
13919     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13920        pvInfoList[index-1].depth == len &&
13921        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13922        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13923     while (*text == '\n') text++;
13924     len = strlen(text);
13925     while (len > 0 && text[len - 1] == '\n') len--;
13926
13927     if (commentList[index] != NULL)
13928       free(commentList[index]);
13929
13930     if (len == 0) {
13931         commentList[index] = NULL;
13932         return;
13933     }
13934   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13935       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13936       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13937     commentList[index] = (char *) malloc(len + 2);
13938     strncpy(commentList[index], text, len);
13939     commentList[index][len] = '\n';
13940     commentList[index][len + 1] = NULLCHAR;
13941   } else {
13942     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13943     char *p;
13944     commentList[index] = (char *) malloc(len + 7);
13945     safeStrCpy(commentList[index], "{\n", 3);
13946     safeStrCpy(commentList[index]+2, text, len+1);
13947     commentList[index][len+2] = NULLCHAR;
13948     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13949     strcat(commentList[index], "\n}\n");
13950   }
13951 }
13952
13953 void
13954 CrushCRs(text)
13955      char *text;
13956 {
13957   char *p = text;
13958   char *q = text;
13959   char ch;
13960
13961   do {
13962     ch = *p++;
13963     if (ch == '\r') continue;
13964     *q++ = ch;
13965   } while (ch != '\0');
13966 }
13967
13968 void
13969 AppendComment(index, text, addBraces)
13970      int index;
13971      char *text;
13972      Boolean addBraces; // [HGM] braces: tells if we should add {}
13973 {
13974     int oldlen, len;
13975     char *old;
13976
13977 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13978     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13979
13980     CrushCRs(text);
13981     while (*text == '\n') text++;
13982     len = strlen(text);
13983     while (len > 0 && text[len - 1] == '\n') len--;
13984
13985     if (len == 0) return;
13986
13987     if (commentList[index] != NULL) {
13988         old = commentList[index];
13989         oldlen = strlen(old);
13990         while(commentList[index][oldlen-1] ==  '\n')
13991           commentList[index][--oldlen] = NULLCHAR;
13992         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13993         safeStrCpy(commentList[index], old, oldlen + len + 6);
13994         free(old);
13995         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13996         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13997           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13998           while (*text == '\n') { text++; len--; }
13999           commentList[index][--oldlen] = NULLCHAR;
14000       }
14001         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14002         else          strcat(commentList[index], "\n");
14003         strcat(commentList[index], text);
14004         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14005         else          strcat(commentList[index], "\n");
14006     } else {
14007         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14008         if(addBraces)
14009           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14010         else commentList[index][0] = NULLCHAR;
14011         strcat(commentList[index], text);
14012         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14013         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14014     }
14015 }
14016
14017 static char * FindStr( char * text, char * sub_text )
14018 {
14019     char * result = strstr( text, sub_text );
14020
14021     if( result != NULL ) {
14022         result += strlen( sub_text );
14023     }
14024
14025     return result;
14026 }
14027
14028 /* [AS] Try to extract PV info from PGN comment */
14029 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14030 char *GetInfoFromComment( int index, char * text )
14031 {
14032     char * sep = text, *p;
14033
14034     if( text != NULL && index > 0 ) {
14035         int score = 0;
14036         int depth = 0;
14037         int time = -1, sec = 0, deci;
14038         char * s_eval = FindStr( text, "[%eval " );
14039         char * s_emt = FindStr( text, "[%emt " );
14040
14041         if( s_eval != NULL || s_emt != NULL ) {
14042             /* New style */
14043             char delim;
14044
14045             if( s_eval != NULL ) {
14046                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14047                     return text;
14048                 }
14049
14050                 if( delim != ']' ) {
14051                     return text;
14052                 }
14053             }
14054
14055             if( s_emt != NULL ) {
14056             }
14057                 return text;
14058         }
14059         else {
14060             /* We expect something like: [+|-]nnn.nn/dd */
14061             int score_lo = 0;
14062
14063             if(*text != '{') return text; // [HGM] braces: must be normal comment
14064
14065             sep = strchr( text, '/' );
14066             if( sep == NULL || sep < (text+4) ) {
14067                 return text;
14068             }
14069
14070             p = text;
14071             if(p[1] == '(') { // comment starts with PV
14072                p = strchr(p, ')'); // locate end of PV
14073                if(p == NULL || sep < p+5) return text;
14074                // at this point we have something like "{(.*) +0.23/6 ..."
14075                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14076                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14077                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14078             }
14079             time = -1; sec = -1; deci = -1;
14080             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14081                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14082                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14083                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14084                 return text;
14085             }
14086
14087             if( score_lo < 0 || score_lo >= 100 ) {
14088                 return text;
14089             }
14090
14091             if(sec >= 0) time = 600*time + 10*sec; else
14092             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14093
14094             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14095
14096             /* [HGM] PV time: now locate end of PV info */
14097             while( *++sep >= '0' && *sep <= '9'); // strip depth
14098             if(time >= 0)
14099             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14100             if(sec >= 0)
14101             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14102             if(deci >= 0)
14103             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14104             while(*sep == ' ') sep++;
14105         }
14106
14107         if( depth <= 0 ) {
14108             return text;
14109         }
14110
14111         if( time < 0 ) {
14112             time = -1;
14113         }
14114
14115         pvInfoList[index-1].depth = depth;
14116         pvInfoList[index-1].score = score;
14117         pvInfoList[index-1].time  = 10*time; // centi-sec
14118         if(*sep == '}') *sep = 0; else *--sep = '{';
14119         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14120     }
14121     return sep;
14122 }
14123
14124 void
14125 SendToProgram(message, cps)
14126      char *message;
14127      ChessProgramState *cps;
14128 {
14129     int count, outCount, error;
14130     char buf[MSG_SIZ];
14131
14132     if (cps->pr == NULL) return;
14133     Attention(cps);
14134
14135     if (appData.debugMode) {
14136         TimeMark now;
14137         GetTimeMark(&now);
14138         fprintf(debugFP, "%ld >%-6s: %s",
14139                 SubtractTimeMarks(&now, &programStartTime),
14140                 cps->which, message);
14141     }
14142
14143     count = strlen(message);
14144     outCount = OutputToProcess(cps->pr, message, count, &error);
14145     if (outCount < count && !exiting
14146                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14147       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14148       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14149         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14150             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14151                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14152                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14153                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14154             } else {
14155                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14156                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14157                 gameInfo.result = res;
14158             }
14159             gameInfo.resultDetails = StrSave(buf);
14160         }
14161         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14162         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14163     }
14164 }
14165
14166 void
14167 ReceiveFromProgram(isr, closure, message, count, error)
14168      InputSourceRef isr;
14169      VOIDSTAR closure;
14170      char *message;
14171      int count;
14172      int error;
14173 {
14174     char *end_str;
14175     char buf[MSG_SIZ];
14176     ChessProgramState *cps = (ChessProgramState *)closure;
14177
14178     if (isr != cps->isr) return; /* Killed intentionally */
14179     if (count <= 0) {
14180         if (count == 0) {
14181             RemoveInputSource(cps->isr);
14182             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14183                     _(cps->which), cps->program);
14184         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14185                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14186                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14187                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14188                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14189                 } else {
14190                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14191                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14192                     gameInfo.result = res;
14193                 }
14194                 gameInfo.resultDetails = StrSave(buf);
14195             }
14196             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14197             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14198         } else {
14199             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14200                     _(cps->which), cps->program);
14201             RemoveInputSource(cps->isr);
14202
14203             /* [AS] Program is misbehaving badly... kill it */
14204             if( count == -2 ) {
14205                 DestroyChildProcess( cps->pr, 9 );
14206                 cps->pr = NoProc;
14207             }
14208
14209             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14210         }
14211         return;
14212     }
14213
14214     if ((end_str = strchr(message, '\r')) != NULL)
14215       *end_str = NULLCHAR;
14216     if ((end_str = strchr(message, '\n')) != NULL)
14217       *end_str = NULLCHAR;
14218
14219     if (appData.debugMode) {
14220         TimeMark now; int print = 1;
14221         char *quote = ""; char c; int i;
14222
14223         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14224                 char start = message[0];
14225                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14226                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14227                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14228                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14229                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14230                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14231                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14232                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14233                    sscanf(message, "hint: %c", &c)!=1 && 
14234                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14235                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14236                     print = (appData.engineComments >= 2);
14237                 }
14238                 message[0] = start; // restore original message
14239         }
14240         if(print) {
14241                 GetTimeMark(&now);
14242                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14243                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14244                         quote,
14245                         message);
14246         }
14247     }
14248
14249     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14250     if (appData.icsEngineAnalyze) {
14251         if (strstr(message, "whisper") != NULL ||
14252              strstr(message, "kibitz") != NULL ||
14253             strstr(message, "tellics") != NULL) return;
14254     }
14255
14256     HandleMachineMove(message, cps);
14257 }
14258
14259
14260 void
14261 SendTimeControl(cps, mps, tc, inc, sd, st)
14262      ChessProgramState *cps;
14263      int mps, inc, sd, st;
14264      long tc;
14265 {
14266     char buf[MSG_SIZ];
14267     int seconds;
14268
14269     if( timeControl_2 > 0 ) {
14270         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14271             tc = timeControl_2;
14272         }
14273     }
14274     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14275     inc /= cps->timeOdds;
14276     st  /= cps->timeOdds;
14277
14278     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14279
14280     if (st > 0) {
14281       /* Set exact time per move, normally using st command */
14282       if (cps->stKludge) {
14283         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14284         seconds = st % 60;
14285         if (seconds == 0) {
14286           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14287         } else {
14288           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14289         }
14290       } else {
14291         snprintf(buf, MSG_SIZ, "st %d\n", st);
14292       }
14293     } else {
14294       /* Set conventional or incremental time control, using level command */
14295       if (seconds == 0) {
14296         /* Note old gnuchess bug -- minutes:seconds used to not work.
14297            Fixed in later versions, but still avoid :seconds
14298            when seconds is 0. */
14299         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14300       } else {
14301         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14302                  seconds, inc/1000.);
14303       }
14304     }
14305     SendToProgram(buf, cps);
14306
14307     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14308     /* Orthogonally, limit search to given depth */
14309     if (sd > 0) {
14310       if (cps->sdKludge) {
14311         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14312       } else {
14313         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14314       }
14315       SendToProgram(buf, cps);
14316     }
14317
14318     if(cps->nps >= 0) { /* [HGM] nps */
14319         if(cps->supportsNPS == FALSE)
14320           cps->nps = -1; // don't use if engine explicitly says not supported!
14321         else {
14322           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14323           SendToProgram(buf, cps);
14324         }
14325     }
14326 }
14327
14328 ChessProgramState *WhitePlayer()
14329 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14330 {
14331     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14332        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14333         return &second;
14334     return &first;
14335 }
14336
14337 void
14338 SendTimeRemaining(cps, machineWhite)
14339      ChessProgramState *cps;
14340      int /*boolean*/ machineWhite;
14341 {
14342     char message[MSG_SIZ];
14343     long time, otime;
14344
14345     /* Note: this routine must be called when the clocks are stopped
14346        or when they have *just* been set or switched; otherwise
14347        it will be off by the time since the current tick started.
14348     */
14349     if (machineWhite) {
14350         time = whiteTimeRemaining / 10;
14351         otime = blackTimeRemaining / 10;
14352     } else {
14353         time = blackTimeRemaining / 10;
14354         otime = whiteTimeRemaining / 10;
14355     }
14356     /* [HGM] translate opponent's time by time-odds factor */
14357     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14358     if (appData.debugMode) {
14359         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14360     }
14361
14362     if (time <= 0) time = 1;
14363     if (otime <= 0) otime = 1;
14364
14365     snprintf(message, MSG_SIZ, "time %ld\n", time);
14366     SendToProgram(message, cps);
14367
14368     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14369     SendToProgram(message, cps);
14370 }
14371
14372 int
14373 BoolFeature(p, name, loc, cps)
14374      char **p;
14375      char *name;
14376      int *loc;
14377      ChessProgramState *cps;
14378 {
14379   char buf[MSG_SIZ];
14380   int len = strlen(name);
14381   int val;
14382
14383   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14384     (*p) += len + 1;
14385     sscanf(*p, "%d", &val);
14386     *loc = (val != 0);
14387     while (**p && **p != ' ')
14388       (*p)++;
14389     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14390     SendToProgram(buf, cps);
14391     return TRUE;
14392   }
14393   return FALSE;
14394 }
14395
14396 int
14397 IntFeature(p, name, loc, cps)
14398      char **p;
14399      char *name;
14400      int *loc;
14401      ChessProgramState *cps;
14402 {
14403   char buf[MSG_SIZ];
14404   int len = strlen(name);
14405   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14406     (*p) += len + 1;
14407     sscanf(*p, "%d", loc);
14408     while (**p && **p != ' ') (*p)++;
14409     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14410     SendToProgram(buf, cps);
14411     return TRUE;
14412   }
14413   return FALSE;
14414 }
14415
14416 int
14417 StringFeature(p, name, loc, cps)
14418      char **p;
14419      char *name;
14420      char loc[];
14421      ChessProgramState *cps;
14422 {
14423   char buf[MSG_SIZ];
14424   int len = strlen(name);
14425   if (strncmp((*p), name, len) == 0
14426       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14427     (*p) += len + 2;
14428     sscanf(*p, "%[^\"]", loc);
14429     while (**p && **p != '\"') (*p)++;
14430     if (**p == '\"') (*p)++;
14431     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14432     SendToProgram(buf, cps);
14433     return TRUE;
14434   }
14435   return FALSE;
14436 }
14437
14438 int
14439 ParseOption(Option *opt, ChessProgramState *cps)
14440 // [HGM] options: process the string that defines an engine option, and determine
14441 // name, type, default value, and allowed value range
14442 {
14443         char *p, *q, buf[MSG_SIZ];
14444         int n, min = (-1)<<31, max = 1<<31, def;
14445
14446         if(p = strstr(opt->name, " -spin ")) {
14447             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14448             if(max < min) max = min; // enforce consistency
14449             if(def < min) def = min;
14450             if(def > max) def = max;
14451             opt->value = def;
14452             opt->min = min;
14453             opt->max = max;
14454             opt->type = Spin;
14455         } else if((p = strstr(opt->name, " -slider "))) {
14456             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14457             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14458             if(max < min) max = min; // enforce consistency
14459             if(def < min) def = min;
14460             if(def > max) def = max;
14461             opt->value = def;
14462             opt->min = min;
14463             opt->max = max;
14464             opt->type = Spin; // Slider;
14465         } else if((p = strstr(opt->name, " -string "))) {
14466             opt->textValue = p+9;
14467             opt->type = TextBox;
14468         } else if((p = strstr(opt->name, " -file "))) {
14469             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14470             opt->textValue = p+7;
14471             opt->type = FileName; // FileName;
14472         } else if((p = strstr(opt->name, " -path "))) {
14473             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14474             opt->textValue = p+7;
14475             opt->type = PathName; // PathName;
14476         } else if(p = strstr(opt->name, " -check ")) {
14477             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14478             opt->value = (def != 0);
14479             opt->type = CheckBox;
14480         } else if(p = strstr(opt->name, " -combo ")) {
14481             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14482             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14483             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14484             opt->value = n = 0;
14485             while(q = StrStr(q, " /// ")) {
14486                 n++; *q = 0;    // count choices, and null-terminate each of them
14487                 q += 5;
14488                 if(*q == '*') { // remember default, which is marked with * prefix
14489                     q++;
14490                     opt->value = n;
14491                 }
14492                 cps->comboList[cps->comboCnt++] = q;
14493             }
14494             cps->comboList[cps->comboCnt++] = NULL;
14495             opt->max = n + 1;
14496             opt->type = ComboBox;
14497         } else if(p = strstr(opt->name, " -button")) {
14498             opt->type = Button;
14499         } else if(p = strstr(opt->name, " -save")) {
14500             opt->type = SaveButton;
14501         } else return FALSE;
14502         *p = 0; // terminate option name
14503         // now look if the command-line options define a setting for this engine option.
14504         if(cps->optionSettings && cps->optionSettings[0])
14505             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14506         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14507           snprintf(buf, MSG_SIZ, "option %s", p);
14508                 if(p = strstr(buf, ",")) *p = 0;
14509                 if(q = strchr(buf, '=')) switch(opt->type) {
14510                     case ComboBox:
14511                         for(n=0; n<opt->max; n++)
14512                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14513                         break;
14514                     case TextBox:
14515                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14516                         break;
14517                     case Spin:
14518                     case CheckBox:
14519                         opt->value = atoi(q+1);
14520                     default:
14521                         break;
14522                 }
14523                 strcat(buf, "\n");
14524                 SendToProgram(buf, cps);
14525         }
14526         return TRUE;
14527 }
14528
14529 void
14530 FeatureDone(cps, val)
14531      ChessProgramState* cps;
14532      int val;
14533 {
14534   DelayedEventCallback cb = GetDelayedEvent();
14535   if ((cb == InitBackEnd3 && cps == &first) ||
14536       (cb == SettingsMenuIfReady && cps == &second) ||
14537       (cb == LoadEngine) ||
14538       (cb == TwoMachinesEventIfReady)) {
14539     CancelDelayedEvent();
14540     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14541   }
14542   cps->initDone = val;
14543 }
14544
14545 /* Parse feature command from engine */
14546 void
14547 ParseFeatures(args, cps)
14548      char* args;
14549      ChessProgramState *cps;
14550 {
14551   char *p = args;
14552   char *q;
14553   int val;
14554   char buf[MSG_SIZ];
14555
14556   for (;;) {
14557     while (*p == ' ') p++;
14558     if (*p == NULLCHAR) return;
14559
14560     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14561     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14562     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14563     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14564     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14565     if (BoolFeature(&p, "reuse", &val, cps)) {
14566       /* Engine can disable reuse, but can't enable it if user said no */
14567       if (!val) cps->reuse = FALSE;
14568       continue;
14569     }
14570     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14571     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14572       if (gameMode == TwoMachinesPlay) {
14573         DisplayTwoMachinesTitle();
14574       } else {
14575         DisplayTitle("");
14576       }
14577       continue;
14578     }
14579     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14580     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14581     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14582     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14583     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14584     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14585     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14586     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14587     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14588     if (IntFeature(&p, "done", &val, cps)) {
14589       FeatureDone(cps, val);
14590       continue;
14591     }
14592     /* Added by Tord: */
14593     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14594     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14595     /* End of additions by Tord */
14596
14597     /* [HGM] added features: */
14598     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14599     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14600     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14601     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14602     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14603     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14604     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14605         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14606           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14607             SendToProgram(buf, cps);
14608             continue;
14609         }
14610         if(cps->nrOptions >= MAX_OPTIONS) {
14611             cps->nrOptions--;
14612             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14613             DisplayError(buf, 0);
14614         }
14615         continue;
14616     }
14617     /* End of additions by HGM */
14618
14619     /* unknown feature: complain and skip */
14620     q = p;
14621     while (*q && *q != '=') q++;
14622     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14623     SendToProgram(buf, cps);
14624     p = q;
14625     if (*p == '=') {
14626       p++;
14627       if (*p == '\"') {
14628         p++;
14629         while (*p && *p != '\"') p++;
14630         if (*p == '\"') p++;
14631       } else {
14632         while (*p && *p != ' ') p++;
14633       }
14634     }
14635   }
14636
14637 }
14638
14639 void
14640 PeriodicUpdatesEvent(newState)
14641      int newState;
14642 {
14643     if (newState == appData.periodicUpdates)
14644       return;
14645
14646     appData.periodicUpdates=newState;
14647
14648     /* Display type changes, so update it now */
14649 //    DisplayAnalysis();
14650
14651     /* Get the ball rolling again... */
14652     if (newState) {
14653         AnalysisPeriodicEvent(1);
14654         StartAnalysisClock();
14655     }
14656 }
14657
14658 void
14659 PonderNextMoveEvent(newState)
14660      int newState;
14661 {
14662     if (newState == appData.ponderNextMove) return;
14663     if (gameMode == EditPosition) EditPositionDone(TRUE);
14664     if (newState) {
14665         SendToProgram("hard\n", &first);
14666         if (gameMode == TwoMachinesPlay) {
14667             SendToProgram("hard\n", &second);
14668         }
14669     } else {
14670         SendToProgram("easy\n", &first);
14671         thinkOutput[0] = NULLCHAR;
14672         if (gameMode == TwoMachinesPlay) {
14673             SendToProgram("easy\n", &second);
14674         }
14675     }
14676     appData.ponderNextMove = newState;
14677 }
14678
14679 void
14680 NewSettingEvent(option, feature, command, value)
14681      char *command;
14682      int option, value, *feature;
14683 {
14684     char buf[MSG_SIZ];
14685
14686     if (gameMode == EditPosition) EditPositionDone(TRUE);
14687     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14688     if(feature == NULL || *feature) SendToProgram(buf, &first);
14689     if (gameMode == TwoMachinesPlay) {
14690         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14691     }
14692 }
14693
14694 void
14695 ShowThinkingEvent()
14696 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14697 {
14698     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14699     int newState = appData.showThinking
14700         // [HGM] thinking: other features now need thinking output as well
14701         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14702
14703     if (oldState == newState) return;
14704     oldState = newState;
14705     if (gameMode == EditPosition) EditPositionDone(TRUE);
14706     if (oldState) {
14707         SendToProgram("post\n", &first);
14708         if (gameMode == TwoMachinesPlay) {
14709             SendToProgram("post\n", &second);
14710         }
14711     } else {
14712         SendToProgram("nopost\n", &first);
14713         thinkOutput[0] = NULLCHAR;
14714         if (gameMode == TwoMachinesPlay) {
14715             SendToProgram("nopost\n", &second);
14716         }
14717     }
14718 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14719 }
14720
14721 void
14722 AskQuestionEvent(title, question, replyPrefix, which)
14723      char *title; char *question; char *replyPrefix; char *which;
14724 {
14725   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14726   if (pr == NoProc) return;
14727   AskQuestion(title, question, replyPrefix, pr);
14728 }
14729
14730 void
14731 TypeInEvent(char firstChar)
14732 {
14733     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14734         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14735         gameMode == AnalyzeMode || gameMode == EditGame || \r
14736         gameMode == EditPosition || gameMode == IcsExamining ||\r
14737         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14738         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14739                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14740                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14741         gameMode == Training) PopUpMoveDialog(firstChar);
14742 }
14743
14744 void
14745 TypeInDoneEvent(char *move)
14746 {
14747         Board board;
14748         int n, fromX, fromY, toX, toY;
14749         char promoChar;
14750         ChessMove moveType;\r
14751
14752         // [HGM] FENedit\r
14753         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14754                 EditPositionPasteFEN(move);\r
14755                 return;\r
14756         }\r
14757         // [HGM] movenum: allow move number to be typed in any mode\r
14758         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14759           ToNrEvent(2*n-1);\r
14760           return;\r
14761         }\r
14762
14763       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14764         gameMode != Training) {\r
14765         DisplayMoveError(_("Displayed move is not current"));\r
14766       } else {\r
14767         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14768           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14769         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14770         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14771           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14772           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14773         } else {\r
14774           DisplayMoveError(_("Could not parse move"));\r
14775         }
14776       }\r
14777 }\r
14778
14779 void
14780 DisplayMove(moveNumber)
14781      int moveNumber;
14782 {
14783     char message[MSG_SIZ];
14784     char res[MSG_SIZ];
14785     char cpThinkOutput[MSG_SIZ];
14786
14787     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14788
14789     if (moveNumber == forwardMostMove - 1 ||
14790         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14791
14792         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14793
14794         if (strchr(cpThinkOutput, '\n')) {
14795             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14796         }
14797     } else {
14798         *cpThinkOutput = NULLCHAR;
14799     }
14800
14801     /* [AS] Hide thinking from human user */
14802     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14803         *cpThinkOutput = NULLCHAR;
14804         if( thinkOutput[0] != NULLCHAR ) {
14805             int i;
14806
14807             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14808                 cpThinkOutput[i] = '.';
14809             }
14810             cpThinkOutput[i] = NULLCHAR;
14811             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14812         }
14813     }
14814
14815     if (moveNumber == forwardMostMove - 1 &&
14816         gameInfo.resultDetails != NULL) {
14817         if (gameInfo.resultDetails[0] == NULLCHAR) {
14818           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14819         } else {
14820           snprintf(res, MSG_SIZ, " {%s} %s",
14821                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14822         }
14823     } else {
14824         res[0] = NULLCHAR;
14825     }
14826
14827     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14828         DisplayMessage(res, cpThinkOutput);
14829     } else {
14830       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14831                 WhiteOnMove(moveNumber) ? " " : ".. ",
14832                 parseList[moveNumber], res);
14833         DisplayMessage(message, cpThinkOutput);
14834     }
14835 }
14836
14837 void
14838 DisplayComment(moveNumber, text)
14839      int moveNumber;
14840      char *text;
14841 {
14842     char title[MSG_SIZ];
14843     char buf[8000]; // comment can be long!
14844     int score, depth;
14845
14846     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14847       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14848     } else {
14849       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14850               WhiteOnMove(moveNumber) ? " " : ".. ",
14851               parseList[moveNumber]);
14852     }
14853     // [HGM] PV info: display PV info together with (or as) comment
14854     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14855       if(text == NULL) text = "";
14856       score = pvInfoList[moveNumber].score;
14857       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14858               depth, (pvInfoList[moveNumber].time+50)/100, text);
14859       text = buf;
14860     }
14861     if (text != NULL && (appData.autoDisplayComment || commentUp))
14862         CommentPopUp(title, text);
14863 }
14864
14865 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14866  * might be busy thinking or pondering.  It can be omitted if your
14867  * gnuchess is configured to stop thinking immediately on any user
14868  * input.  However, that gnuchess feature depends on the FIONREAD
14869  * ioctl, which does not work properly on some flavors of Unix.
14870  */
14871 void
14872 Attention(cps)
14873      ChessProgramState *cps;
14874 {
14875 #if ATTENTION
14876     if (!cps->useSigint) return;
14877     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14878     switch (gameMode) {
14879       case MachinePlaysWhite:
14880       case MachinePlaysBlack:
14881       case TwoMachinesPlay:
14882       case IcsPlayingWhite:
14883       case IcsPlayingBlack:
14884       case AnalyzeMode:
14885       case AnalyzeFile:
14886         /* Skip if we know it isn't thinking */
14887         if (!cps->maybeThinking) return;
14888         if (appData.debugMode)
14889           fprintf(debugFP, "Interrupting %s\n", cps->which);
14890         InterruptChildProcess(cps->pr);
14891         cps->maybeThinking = FALSE;
14892         break;
14893       default:
14894         break;
14895     }
14896 #endif /*ATTENTION*/
14897 }
14898
14899 int
14900 CheckFlags()
14901 {
14902     if (whiteTimeRemaining <= 0) {
14903         if (!whiteFlag) {
14904             whiteFlag = TRUE;
14905             if (appData.icsActive) {
14906                 if (appData.autoCallFlag &&
14907                     gameMode == IcsPlayingBlack && !blackFlag) {
14908                   SendToICS(ics_prefix);
14909                   SendToICS("flag\n");
14910                 }
14911             } else {
14912                 if (blackFlag) {
14913                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14914                 } else {
14915                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14916                     if (appData.autoCallFlag) {
14917                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14918                         return TRUE;
14919                     }
14920                 }
14921             }
14922         }
14923     }
14924     if (blackTimeRemaining <= 0) {
14925         if (!blackFlag) {
14926             blackFlag = TRUE;
14927             if (appData.icsActive) {
14928                 if (appData.autoCallFlag &&
14929                     gameMode == IcsPlayingWhite && !whiteFlag) {
14930                   SendToICS(ics_prefix);
14931                   SendToICS("flag\n");
14932                 }
14933             } else {
14934                 if (whiteFlag) {
14935                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14936                 } else {
14937                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14938                     if (appData.autoCallFlag) {
14939                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14940                         return TRUE;
14941                     }
14942                 }
14943             }
14944         }
14945     }
14946     return FALSE;
14947 }
14948
14949 void
14950 CheckTimeControl()
14951 {
14952     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14953         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14954
14955     /*
14956      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14957      */
14958     if ( !WhiteOnMove(forwardMostMove) ) {
14959         /* White made time control */
14960         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14961         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14962         /* [HGM] time odds: correct new time quota for time odds! */
14963                                             / WhitePlayer()->timeOdds;
14964         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14965     } else {
14966         lastBlack -= blackTimeRemaining;
14967         /* Black made time control */
14968         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14969                                             / WhitePlayer()->other->timeOdds;
14970         lastWhite = whiteTimeRemaining;
14971     }
14972 }
14973
14974 void
14975 DisplayBothClocks()
14976 {
14977     int wom = gameMode == EditPosition ?
14978       !blackPlaysFirst : WhiteOnMove(currentMove);
14979     DisplayWhiteClock(whiteTimeRemaining, wom);
14980     DisplayBlackClock(blackTimeRemaining, !wom);
14981 }
14982
14983
14984 /* Timekeeping seems to be a portability nightmare.  I think everyone
14985    has ftime(), but I'm really not sure, so I'm including some ifdefs
14986    to use other calls if you don't.  Clocks will be less accurate if
14987    you have neither ftime nor gettimeofday.
14988 */
14989
14990 /* VS 2008 requires the #include outside of the function */
14991 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14992 #include <sys/timeb.h>
14993 #endif
14994
14995 /* Get the current time as a TimeMark */
14996 void
14997 GetTimeMark(tm)
14998      TimeMark *tm;
14999 {
15000 #if HAVE_GETTIMEOFDAY
15001
15002     struct timeval timeVal;
15003     struct timezone timeZone;
15004
15005     gettimeofday(&timeVal, &timeZone);
15006     tm->sec = (long) timeVal.tv_sec;
15007     tm->ms = (int) (timeVal.tv_usec / 1000L);
15008
15009 #else /*!HAVE_GETTIMEOFDAY*/
15010 #if HAVE_FTIME
15011
15012 // include <sys/timeb.h> / moved to just above start of function
15013     struct timeb timeB;
15014
15015     ftime(&timeB);
15016     tm->sec = (long) timeB.time;
15017     tm->ms = (int) timeB.millitm;
15018
15019 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15020     tm->sec = (long) time(NULL);
15021     tm->ms = 0;
15022 #endif
15023 #endif
15024 }
15025
15026 /* Return the difference in milliseconds between two
15027    time marks.  We assume the difference will fit in a long!
15028 */
15029 long
15030 SubtractTimeMarks(tm2, tm1)
15031      TimeMark *tm2, *tm1;
15032 {
15033     return 1000L*(tm2->sec - tm1->sec) +
15034            (long) (tm2->ms - tm1->ms);
15035 }
15036
15037
15038 /*
15039  * Code to manage the game clocks.
15040  *
15041  * In tournament play, black starts the clock and then white makes a move.
15042  * We give the human user a slight advantage if he is playing white---the
15043  * clocks don't run until he makes his first move, so it takes zero time.
15044  * Also, we don't account for network lag, so we could get out of sync
15045  * with GNU Chess's clock -- but then, referees are always right.
15046  */
15047
15048 static TimeMark tickStartTM;
15049 static long intendedTickLength;
15050
15051 long
15052 NextTickLength(timeRemaining)
15053      long timeRemaining;
15054 {
15055     long nominalTickLength, nextTickLength;
15056
15057     if (timeRemaining > 0L && timeRemaining <= 10000L)
15058       nominalTickLength = 100L;
15059     else
15060       nominalTickLength = 1000L;
15061     nextTickLength = timeRemaining % nominalTickLength;
15062     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15063
15064     return nextTickLength;
15065 }
15066
15067 /* Adjust clock one minute up or down */
15068 void
15069 AdjustClock(Boolean which, int dir)
15070 {
15071     if(which) blackTimeRemaining += 60000*dir;
15072     else      whiteTimeRemaining += 60000*dir;
15073     DisplayBothClocks();
15074 }
15075
15076 /* Stop clocks and reset to a fresh time control */
15077 void
15078 ResetClocks()
15079 {
15080     (void) StopClockTimer();
15081     if (appData.icsActive) {
15082         whiteTimeRemaining = blackTimeRemaining = 0;
15083     } else if (searchTime) {
15084         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15085         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15086     } else { /* [HGM] correct new time quote for time odds */
15087         whiteTC = blackTC = fullTimeControlString;
15088         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15089         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15090     }
15091     if (whiteFlag || blackFlag) {
15092         DisplayTitle("");
15093         whiteFlag = blackFlag = FALSE;
15094     }
15095     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15096     DisplayBothClocks();
15097 }
15098
15099 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15100
15101 /* Decrement running clock by amount of time that has passed */
15102 void
15103 DecrementClocks()
15104 {
15105     long timeRemaining;
15106     long lastTickLength, fudge;
15107     TimeMark now;
15108
15109     if (!appData.clockMode) return;
15110     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15111
15112     GetTimeMark(&now);
15113
15114     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15115
15116     /* Fudge if we woke up a little too soon */
15117     fudge = intendedTickLength - lastTickLength;
15118     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15119
15120     if (WhiteOnMove(forwardMostMove)) {
15121         if(whiteNPS >= 0) lastTickLength = 0;
15122         timeRemaining = whiteTimeRemaining -= lastTickLength;
15123         if(timeRemaining < 0 && !appData.icsActive) {
15124             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15125             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15126                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15127                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15128             }
15129         }
15130         DisplayWhiteClock(whiteTimeRemaining - fudge,
15131                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15132     } else {
15133         if(blackNPS >= 0) lastTickLength = 0;
15134         timeRemaining = blackTimeRemaining -= lastTickLength;
15135         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15136             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15137             if(suddenDeath) {
15138                 blackStartMove = forwardMostMove;
15139                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15140             }
15141         }
15142         DisplayBlackClock(blackTimeRemaining - fudge,
15143                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15144     }
15145     if (CheckFlags()) return;
15146
15147     tickStartTM = now;
15148     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15149     StartClockTimer(intendedTickLength);
15150
15151     /* if the time remaining has fallen below the alarm threshold, sound the
15152      * alarm. if the alarm has sounded and (due to a takeback or time control
15153      * with increment) the time remaining has increased to a level above the
15154      * threshold, reset the alarm so it can sound again.
15155      */
15156
15157     if (appData.icsActive && appData.icsAlarm) {
15158
15159         /* make sure we are dealing with the user's clock */
15160         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15161                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15162            )) return;
15163
15164         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15165             alarmSounded = FALSE;
15166         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15167             PlayAlarmSound();
15168             alarmSounded = TRUE;
15169         }
15170     }
15171 }
15172
15173
15174 /* A player has just moved, so stop the previously running
15175    clock and (if in clock mode) start the other one.
15176    We redisplay both clocks in case we're in ICS mode, because
15177    ICS gives us an update to both clocks after every move.
15178    Note that this routine is called *after* forwardMostMove
15179    is updated, so the last fractional tick must be subtracted
15180    from the color that is *not* on move now.
15181 */
15182 void
15183 SwitchClocks(int newMoveNr)
15184 {
15185     long lastTickLength;
15186     TimeMark now;
15187     int flagged = FALSE;
15188
15189     GetTimeMark(&now);
15190
15191     if (StopClockTimer() && appData.clockMode) {
15192         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15193         if (!WhiteOnMove(forwardMostMove)) {
15194             if(blackNPS >= 0) lastTickLength = 0;
15195             blackTimeRemaining -= lastTickLength;
15196            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15197 //         if(pvInfoList[forwardMostMove].time == -1)
15198                  pvInfoList[forwardMostMove].time =               // use GUI time
15199                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15200         } else {
15201            if(whiteNPS >= 0) lastTickLength = 0;
15202            whiteTimeRemaining -= lastTickLength;
15203            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15204 //         if(pvInfoList[forwardMostMove].time == -1)
15205                  pvInfoList[forwardMostMove].time =
15206                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15207         }
15208         flagged = CheckFlags();
15209     }
15210     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15211     CheckTimeControl();
15212
15213     if (flagged || !appData.clockMode) return;
15214
15215     switch (gameMode) {
15216       case MachinePlaysBlack:
15217       case MachinePlaysWhite:
15218       case BeginningOfGame:
15219         if (pausing) return;
15220         break;
15221
15222       case EditGame:
15223       case PlayFromGameFile:
15224       case IcsExamining:
15225         return;
15226
15227       default:
15228         break;
15229     }
15230
15231     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15232         if(WhiteOnMove(forwardMostMove))
15233              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15234         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15235     }
15236
15237     tickStartTM = now;
15238     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15239       whiteTimeRemaining : blackTimeRemaining);
15240     StartClockTimer(intendedTickLength);
15241 }
15242
15243
15244 /* Stop both clocks */
15245 void
15246 StopClocks()
15247 {
15248     long lastTickLength;
15249     TimeMark now;
15250
15251     if (!StopClockTimer()) return;
15252     if (!appData.clockMode) return;
15253
15254     GetTimeMark(&now);
15255
15256     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15257     if (WhiteOnMove(forwardMostMove)) {
15258         if(whiteNPS >= 0) lastTickLength = 0;
15259         whiteTimeRemaining -= lastTickLength;
15260         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15261     } else {
15262         if(blackNPS >= 0) lastTickLength = 0;
15263         blackTimeRemaining -= lastTickLength;
15264         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15265     }
15266     CheckFlags();
15267 }
15268
15269 /* Start clock of player on move.  Time may have been reset, so
15270    if clock is already running, stop and restart it. */
15271 void
15272 StartClocks()
15273 {
15274     (void) StopClockTimer(); /* in case it was running already */
15275     DisplayBothClocks();
15276     if (CheckFlags()) return;
15277
15278     if (!appData.clockMode) return;
15279     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15280
15281     GetTimeMark(&tickStartTM);
15282     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15283       whiteTimeRemaining : blackTimeRemaining);
15284
15285    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15286     whiteNPS = blackNPS = -1;
15287     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15288        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15289         whiteNPS = first.nps;
15290     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15291        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15292         blackNPS = first.nps;
15293     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15294         whiteNPS = second.nps;
15295     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15296         blackNPS = second.nps;
15297     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15298
15299     StartClockTimer(intendedTickLength);
15300 }
15301
15302 char *
15303 TimeString(ms)
15304      long ms;
15305 {
15306     long second, minute, hour, day;
15307     char *sign = "";
15308     static char buf[32];
15309
15310     if (ms > 0 && ms <= 9900) {
15311       /* convert milliseconds to tenths, rounding up */
15312       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15313
15314       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15315       return buf;
15316     }
15317
15318     /* convert milliseconds to seconds, rounding up */
15319     /* use floating point to avoid strangeness of integer division
15320        with negative dividends on many machines */
15321     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15322
15323     if (second < 0) {
15324         sign = "-";
15325         second = -second;
15326     }
15327
15328     day = second / (60 * 60 * 24);
15329     second = second % (60 * 60 * 24);
15330     hour = second / (60 * 60);
15331     second = second % (60 * 60);
15332     minute = second / 60;
15333     second = second % 60;
15334
15335     if (day > 0)
15336       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15337               sign, day, hour, minute, second);
15338     else if (hour > 0)
15339       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15340     else
15341       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15342
15343     return buf;
15344 }
15345
15346
15347 /*
15348  * This is necessary because some C libraries aren't ANSI C compliant yet.
15349  */
15350 char *
15351 StrStr(string, match)
15352      char *string, *match;
15353 {
15354     int i, length;
15355
15356     length = strlen(match);
15357
15358     for (i = strlen(string) - length; i >= 0; i--, string++)
15359       if (!strncmp(match, string, length))
15360         return string;
15361
15362     return NULL;
15363 }
15364
15365 char *
15366 StrCaseStr(string, match)
15367      char *string, *match;
15368 {
15369     int i, j, length;
15370
15371     length = strlen(match);
15372
15373     for (i = strlen(string) - length; i >= 0; i--, string++) {
15374         for (j = 0; j < length; j++) {
15375             if (ToLower(match[j]) != ToLower(string[j]))
15376               break;
15377         }
15378         if (j == length) return string;
15379     }
15380
15381     return NULL;
15382 }
15383
15384 #ifndef _amigados
15385 int
15386 StrCaseCmp(s1, s2)
15387      char *s1, *s2;
15388 {
15389     char c1, c2;
15390
15391     for (;;) {
15392         c1 = ToLower(*s1++);
15393         c2 = ToLower(*s2++);
15394         if (c1 > c2) return 1;
15395         if (c1 < c2) return -1;
15396         if (c1 == NULLCHAR) return 0;
15397     }
15398 }
15399
15400
15401 int
15402 ToLower(c)
15403      int c;
15404 {
15405     return isupper(c) ? tolower(c) : c;
15406 }
15407
15408
15409 int
15410 ToUpper(c)
15411      int c;
15412 {
15413     return islower(c) ? toupper(c) : c;
15414 }
15415 #endif /* !_amigados    */
15416
15417 char *
15418 StrSave(s)
15419      char *s;
15420 {
15421   char *ret;
15422
15423   if ((ret = (char *) malloc(strlen(s) + 1)))
15424     {
15425       safeStrCpy(ret, s, strlen(s)+1);
15426     }
15427   return ret;
15428 }
15429
15430 char *
15431 StrSavePtr(s, savePtr)
15432      char *s, **savePtr;
15433 {
15434     if (*savePtr) {
15435         free(*savePtr);
15436     }
15437     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15438       safeStrCpy(*savePtr, s, strlen(s)+1);
15439     }
15440     return(*savePtr);
15441 }
15442
15443 char *
15444 PGNDate()
15445 {
15446     time_t clock;
15447     struct tm *tm;
15448     char buf[MSG_SIZ];
15449
15450     clock = time((time_t *)NULL);
15451     tm = localtime(&clock);
15452     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15453             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15454     return StrSave(buf);
15455 }
15456
15457
15458 char *
15459 PositionToFEN(move, overrideCastling)
15460      int move;
15461      char *overrideCastling;
15462 {
15463     int i, j, fromX, fromY, toX, toY;
15464     int whiteToPlay;
15465     char buf[128];
15466     char *p, *q;
15467     int emptycount;
15468     ChessSquare piece;
15469
15470     whiteToPlay = (gameMode == EditPosition) ?
15471       !blackPlaysFirst : (move % 2 == 0);
15472     p = buf;
15473
15474     /* Piece placement data */
15475     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15476         emptycount = 0;
15477         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15478             if (boards[move][i][j] == EmptySquare) {
15479                 emptycount++;
15480             } else { ChessSquare piece = boards[move][i][j];
15481                 if (emptycount > 0) {
15482                     if(emptycount<10) /* [HGM] can be >= 10 */
15483                         *p++ = '0' + emptycount;
15484                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15485                     emptycount = 0;
15486                 }
15487                 if(PieceToChar(piece) == '+') {
15488                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15489                     *p++ = '+';
15490                     piece = (ChessSquare)(DEMOTED piece);
15491                 }
15492                 *p++ = PieceToChar(piece);
15493                 if(p[-1] == '~') {
15494                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15495                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15496                     *p++ = '~';
15497                 }
15498             }
15499         }
15500         if (emptycount > 0) {
15501             if(emptycount<10) /* [HGM] can be >= 10 */
15502                 *p++ = '0' + emptycount;
15503             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15504             emptycount = 0;
15505         }
15506         *p++ = '/';
15507     }
15508     *(p - 1) = ' ';
15509
15510     /* [HGM] print Crazyhouse or Shogi holdings */
15511     if( gameInfo.holdingsWidth ) {
15512         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15513         q = p;
15514         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15515             piece = boards[move][i][BOARD_WIDTH-1];
15516             if( piece != EmptySquare )
15517               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15518                   *p++ = PieceToChar(piece);
15519         }
15520         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15521             piece = boards[move][BOARD_HEIGHT-i-1][0];
15522             if( piece != EmptySquare )
15523               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15524                   *p++ = PieceToChar(piece);
15525         }
15526
15527         if( q == p ) *p++ = '-';
15528         *p++ = ']';
15529         *p++ = ' ';
15530     }
15531
15532     /* Active color */
15533     *p++ = whiteToPlay ? 'w' : 'b';
15534     *p++ = ' ';
15535
15536   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15537     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15538   } else {
15539   if(nrCastlingRights) {
15540      q = p;
15541      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15542        /* [HGM] write directly from rights */
15543            if(boards[move][CASTLING][2] != NoRights &&
15544               boards[move][CASTLING][0] != NoRights   )
15545                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15546            if(boards[move][CASTLING][2] != NoRights &&
15547               boards[move][CASTLING][1] != NoRights   )
15548                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15549            if(boards[move][CASTLING][5] != NoRights &&
15550               boards[move][CASTLING][3] != NoRights   )
15551                 *p++ = boards[move][CASTLING][3] + AAA;
15552            if(boards[move][CASTLING][5] != NoRights &&
15553               boards[move][CASTLING][4] != NoRights   )
15554                 *p++ = boards[move][CASTLING][4] + AAA;
15555      } else {
15556
15557         /* [HGM] write true castling rights */
15558         if( nrCastlingRights == 6 ) {
15559             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15560                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15561             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15562                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15563             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15564                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15565             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15566                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15567         }
15568      }
15569      if (q == p) *p++ = '-'; /* No castling rights */
15570      *p++ = ' ';
15571   }
15572
15573   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15574      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15575     /* En passant target square */
15576     if (move > backwardMostMove) {
15577         fromX = moveList[move - 1][0] - AAA;
15578         fromY = moveList[move - 1][1] - ONE;
15579         toX = moveList[move - 1][2] - AAA;
15580         toY = moveList[move - 1][3] - ONE;
15581         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15582             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15583             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15584             fromX == toX) {
15585             /* 2-square pawn move just happened */
15586             *p++ = toX + AAA;
15587             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15588         } else {
15589             *p++ = '-';
15590         }
15591     } else if(move == backwardMostMove) {
15592         // [HGM] perhaps we should always do it like this, and forget the above?
15593         if((signed char)boards[move][EP_STATUS] >= 0) {
15594             *p++ = boards[move][EP_STATUS] + AAA;
15595             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15596         } else {
15597             *p++ = '-';
15598         }
15599     } else {
15600         *p++ = '-';
15601     }
15602     *p++ = ' ';
15603   }
15604   }
15605
15606     /* [HGM] find reversible plies */
15607     {   int i = 0, j=move;
15608
15609         if (appData.debugMode) { int k;
15610             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15611             for(k=backwardMostMove; k<=forwardMostMove; k++)
15612                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15613
15614         }
15615
15616         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15617         if( j == backwardMostMove ) i += initialRulePlies;
15618         sprintf(p, "%d ", i);
15619         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15620     }
15621     /* Fullmove number */
15622     sprintf(p, "%d", (move / 2) + 1);
15623
15624     return StrSave(buf);
15625 }
15626
15627 Boolean
15628 ParseFEN(board, blackPlaysFirst, fen)
15629     Board board;
15630      int *blackPlaysFirst;
15631      char *fen;
15632 {
15633     int i, j;
15634     char *p, c;
15635     int emptycount;
15636     ChessSquare piece;
15637
15638     p = fen;
15639
15640     /* [HGM] by default clear Crazyhouse holdings, if present */
15641     if(gameInfo.holdingsWidth) {
15642        for(i=0; i<BOARD_HEIGHT; i++) {
15643            board[i][0]             = EmptySquare; /* black holdings */
15644            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15645            board[i][1]             = (ChessSquare) 0; /* black counts */
15646            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15647        }
15648     }
15649
15650     /* Piece placement data */
15651     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15652         j = 0;
15653         for (;;) {
15654             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15655                 if (*p == '/') p++;
15656                 emptycount = gameInfo.boardWidth - j;
15657                 while (emptycount--)
15658                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15659                 break;
15660 #if(BOARD_FILES >= 10)
15661             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15662                 p++; emptycount=10;
15663                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15664                 while (emptycount--)
15665                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15666 #endif
15667             } else if (isdigit(*p)) {
15668                 emptycount = *p++ - '0';
15669                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15670                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15671                 while (emptycount--)
15672                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15673             } else if (*p == '+' || isalpha(*p)) {
15674                 if (j >= gameInfo.boardWidth) return FALSE;
15675                 if(*p=='+') {
15676                     piece = CharToPiece(*++p);
15677                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15678                     piece = (ChessSquare) (PROMOTED piece ); p++;
15679                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15680                 } else piece = CharToPiece(*p++);
15681
15682                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15683                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15684                     piece = (ChessSquare) (PROMOTED piece);
15685                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15686                     p++;
15687                 }
15688                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15689             } else {
15690                 return FALSE;
15691             }
15692         }
15693     }
15694     while (*p == '/' || *p == ' ') p++;
15695
15696     /* [HGM] look for Crazyhouse holdings here */
15697     while(*p==' ') p++;
15698     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15699         if(*p == '[') p++;
15700         if(*p == '-' ) p++; /* empty holdings */ else {
15701             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15702             /* if we would allow FEN reading to set board size, we would   */
15703             /* have to add holdings and shift the board read so far here   */
15704             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15705                 p++;
15706                 if((int) piece >= (int) BlackPawn ) {
15707                     i = (int)piece - (int)BlackPawn;
15708                     i = PieceToNumber((ChessSquare)i);
15709                     if( i >= gameInfo.holdingsSize ) return FALSE;
15710                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15711                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15712                 } else {
15713                     i = (int)piece - (int)WhitePawn;
15714                     i = PieceToNumber((ChessSquare)i);
15715                     if( i >= gameInfo.holdingsSize ) return FALSE;
15716                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15717                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15718                 }
15719             }
15720         }
15721         if(*p == ']') p++;
15722     }
15723
15724     while(*p == ' ') p++;
15725
15726     /* Active color */
15727     c = *p++;
15728     if(appData.colorNickNames) {
15729       if( c == appData.colorNickNames[0] ) c = 'w'; else
15730       if( c == appData.colorNickNames[1] ) c = 'b';
15731     }
15732     switch (c) {
15733       case 'w':
15734         *blackPlaysFirst = FALSE;
15735         break;
15736       case 'b':
15737         *blackPlaysFirst = TRUE;
15738         break;
15739       default:
15740         return FALSE;
15741     }
15742
15743     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15744     /* return the extra info in global variiables             */
15745
15746     /* set defaults in case FEN is incomplete */
15747     board[EP_STATUS] = EP_UNKNOWN;
15748     for(i=0; i<nrCastlingRights; i++ ) {
15749         board[CASTLING][i] =
15750             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15751     }   /* assume possible unless obviously impossible */
15752     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15753     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15754     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15755                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15756     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15757     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15758     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15759                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15760     FENrulePlies = 0;
15761
15762     while(*p==' ') p++;
15763     if(nrCastlingRights) {
15764       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15765           /* castling indicator present, so default becomes no castlings */
15766           for(i=0; i<nrCastlingRights; i++ ) {
15767                  board[CASTLING][i] = NoRights;
15768           }
15769       }
15770       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15771              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15772              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15773              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15774         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15775
15776         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15777             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15778             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15779         }
15780         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15781             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15782         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15783                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15784         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15785                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15786         switch(c) {
15787           case'K':
15788               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15789               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15790               board[CASTLING][2] = whiteKingFile;
15791               break;
15792           case'Q':
15793               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15794               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15795               board[CASTLING][2] = whiteKingFile;
15796               break;
15797           case'k':
15798               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15799               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15800               board[CASTLING][5] = blackKingFile;
15801               break;
15802           case'q':
15803               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15804               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15805               board[CASTLING][5] = blackKingFile;
15806           case '-':
15807               break;
15808           default: /* FRC castlings */
15809               if(c >= 'a') { /* black rights */
15810                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15811                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15812                   if(i == BOARD_RGHT) break;
15813                   board[CASTLING][5] = i;
15814                   c -= AAA;
15815                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15816                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15817                   if(c > i)
15818                       board[CASTLING][3] = c;
15819                   else
15820                       board[CASTLING][4] = c;
15821               } else { /* white rights */
15822                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15823                     if(board[0][i] == WhiteKing) break;
15824                   if(i == BOARD_RGHT) break;
15825                   board[CASTLING][2] = i;
15826                   c -= AAA - 'a' + 'A';
15827                   if(board[0][c] >= WhiteKing) break;
15828                   if(c > i)
15829                       board[CASTLING][0] = c;
15830                   else
15831                       board[CASTLING][1] = c;
15832               }
15833         }
15834       }
15835       for(i=0; i<nrCastlingRights; i++)
15836         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15837     if (appData.debugMode) {
15838         fprintf(debugFP, "FEN castling rights:");
15839         for(i=0; i<nrCastlingRights; i++)
15840         fprintf(debugFP, " %d", board[CASTLING][i]);
15841         fprintf(debugFP, "\n");
15842     }
15843
15844       while(*p==' ') p++;
15845     }
15846
15847     /* read e.p. field in games that know e.p. capture */
15848     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15849        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15850       if(*p=='-') {
15851         p++; board[EP_STATUS] = EP_NONE;
15852       } else {
15853          char c = *p++ - AAA;
15854
15855          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15856          if(*p >= '0' && *p <='9') p++;
15857          board[EP_STATUS] = c;
15858       }
15859     }
15860
15861
15862     if(sscanf(p, "%d", &i) == 1) {
15863         FENrulePlies = i; /* 50-move ply counter */
15864         /* (The move number is still ignored)    */
15865     }
15866
15867     return TRUE;
15868 }
15869
15870 void
15871 EditPositionPasteFEN(char *fen)
15872 {
15873   if (fen != NULL) {
15874     Board initial_position;
15875
15876     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15877       DisplayError(_("Bad FEN position in clipboard"), 0);
15878       return ;
15879     } else {
15880       int savedBlackPlaysFirst = blackPlaysFirst;
15881       EditPositionEvent();
15882       blackPlaysFirst = savedBlackPlaysFirst;
15883       CopyBoard(boards[0], initial_position);
15884       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15885       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15886       DisplayBothClocks();
15887       DrawPosition(FALSE, boards[currentMove]);
15888     }
15889   }
15890 }
15891
15892 static char cseq[12] = "\\   ";
15893
15894 Boolean set_cont_sequence(char *new_seq)
15895 {
15896     int len;
15897     Boolean ret;
15898
15899     // handle bad attempts to set the sequence
15900         if (!new_seq)
15901                 return 0; // acceptable error - no debug
15902
15903     len = strlen(new_seq);
15904     ret = (len > 0) && (len < sizeof(cseq));
15905     if (ret)
15906       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15907     else if (appData.debugMode)
15908       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15909     return ret;
15910 }
15911
15912 /*
15913     reformat a source message so words don't cross the width boundary.  internal
15914     newlines are not removed.  returns the wrapped size (no null character unless
15915     included in source message).  If dest is NULL, only calculate the size required
15916     for the dest buffer.  lp argument indicats line position upon entry, and it's
15917     passed back upon exit.
15918 */
15919 int wrap(char *dest, char *src, int count, int width, int *lp)
15920 {
15921     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15922
15923     cseq_len = strlen(cseq);
15924     old_line = line = *lp;
15925     ansi = len = clen = 0;
15926
15927     for (i=0; i < count; i++)
15928     {
15929         if (src[i] == '\033')
15930             ansi = 1;
15931
15932         // if we hit the width, back up
15933         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15934         {
15935             // store i & len in case the word is too long
15936             old_i = i, old_len = len;
15937
15938             // find the end of the last word
15939             while (i && src[i] != ' ' && src[i] != '\n')
15940             {
15941                 i--;
15942                 len--;
15943             }
15944
15945             // word too long?  restore i & len before splitting it
15946             if ((old_i-i+clen) >= width)
15947             {
15948                 i = old_i;
15949                 len = old_len;
15950             }
15951
15952             // extra space?
15953             if (i && src[i-1] == ' ')
15954                 len--;
15955
15956             if (src[i] != ' ' && src[i] != '\n')
15957             {
15958                 i--;
15959                 if (len)
15960                     len--;
15961             }
15962
15963             // now append the newline and continuation sequence
15964             if (dest)
15965                 dest[len] = '\n';
15966             len++;
15967             if (dest)
15968                 strncpy(dest+len, cseq, cseq_len);
15969             len += cseq_len;
15970             line = cseq_len;
15971             clen = cseq_len;
15972             continue;
15973         }
15974
15975         if (dest)
15976             dest[len] = src[i];
15977         len++;
15978         if (!ansi)
15979             line++;
15980         if (src[i] == '\n')
15981             line = 0;
15982         if (src[i] == 'm')
15983             ansi = 0;
15984     }
15985     if (dest && appData.debugMode)
15986     {
15987         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15988             count, width, line, len, *lp);
15989         show_bytes(debugFP, src, count);
15990         fprintf(debugFP, "\ndest: ");
15991         show_bytes(debugFP, dest, len);
15992         fprintf(debugFP, "\n");
15993     }
15994     *lp = dest ? line : old_line;
15995
15996     return len;
15997 }
15998
15999 // [HGM] vari: routines for shelving variations
16000
16001 void
16002 PushTail(int firstMove, int lastMove)
16003 {
16004         int i, j, nrMoves = lastMove - firstMove;
16005
16006         if(appData.icsActive) { // only in local mode
16007                 forwardMostMove = currentMove; // mimic old ICS behavior
16008                 return;
16009         }
16010         if(storedGames >= MAX_VARIATIONS-1) return;
16011
16012         // push current tail of game on stack
16013         savedResult[storedGames] = gameInfo.result;
16014         savedDetails[storedGames] = gameInfo.resultDetails;
16015         gameInfo.resultDetails = NULL;
16016         savedFirst[storedGames] = firstMove;
16017         savedLast [storedGames] = lastMove;
16018         savedFramePtr[storedGames] = framePtr;
16019         framePtr -= nrMoves; // reserve space for the boards
16020         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16021             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16022             for(j=0; j<MOVE_LEN; j++)
16023                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16024             for(j=0; j<2*MOVE_LEN; j++)
16025                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16026             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16027             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16028             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16029             pvInfoList[firstMove+i-1].depth = 0;
16030             commentList[framePtr+i] = commentList[firstMove+i];
16031             commentList[firstMove+i] = NULL;
16032         }
16033
16034         storedGames++;
16035         forwardMostMove = firstMove; // truncate game so we can start variation
16036         if(storedGames == 1) GreyRevert(FALSE);
16037 }
16038
16039 Boolean
16040 PopTail(Boolean annotate)
16041 {
16042         int i, j, nrMoves;
16043         char buf[8000], moveBuf[20];
16044
16045         if(appData.icsActive) return FALSE; // only in local mode
16046         if(!storedGames) return FALSE; // sanity
16047         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16048
16049         storedGames--;
16050         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16051         nrMoves = savedLast[storedGames] - currentMove;
16052         if(annotate) {
16053                 int cnt = 10;
16054                 if(!WhiteOnMove(currentMove))
16055                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16056                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16057                 for(i=currentMove; i<forwardMostMove; i++) {
16058                         if(WhiteOnMove(i))
16059                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16060                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16061                         strcat(buf, moveBuf);
16062                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16063                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16064                 }
16065                 strcat(buf, ")");
16066         }
16067         for(i=1; i<=nrMoves; i++) { // copy last variation back
16068             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16069             for(j=0; j<MOVE_LEN; j++)
16070                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16071             for(j=0; j<2*MOVE_LEN; j++)
16072                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16073             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16074             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16075             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16076             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16077             commentList[currentMove+i] = commentList[framePtr+i];
16078             commentList[framePtr+i] = NULL;
16079         }
16080         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16081         framePtr = savedFramePtr[storedGames];
16082         gameInfo.result = savedResult[storedGames];
16083         if(gameInfo.resultDetails != NULL) {
16084             free(gameInfo.resultDetails);
16085       }
16086         gameInfo.resultDetails = savedDetails[storedGames];
16087         forwardMostMove = currentMove + nrMoves;
16088         if(storedGames == 0) GreyRevert(TRUE);
16089         return TRUE;
16090 }
16091
16092 void
16093 CleanupTail()
16094 {       // remove all shelved variations
16095         int i;
16096         for(i=0; i<storedGames; i++) {
16097             if(savedDetails[i])
16098                 free(savedDetails[i]);
16099             savedDetails[i] = NULL;
16100         }
16101         for(i=framePtr; i<MAX_MOVES; i++) {
16102                 if(commentList[i]) free(commentList[i]);
16103                 commentList[i] = NULL;
16104         }
16105         framePtr = MAX_MOVES-1;
16106         storedGames = 0;
16107 }
16108
16109 void
16110 LoadVariation(int index, char *text)
16111 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16112         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16113         int level = 0, move;
16114
16115         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16116         // first find outermost bracketing variation
16117         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16118             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16119                 if(*p == '{') wait = '}'; else
16120                 if(*p == '[') wait = ']'; else
16121                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16122                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16123             }
16124             if(*p == wait) wait = NULLCHAR; // closing ]} found
16125             p++;
16126         }
16127         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16128         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16129         end[1] = NULLCHAR; // clip off comment beyond variation
16130         ToNrEvent(currentMove-1);
16131         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16132         // kludge: use ParsePV() to append variation to game
16133         move = currentMove;
16134         ParsePV(start, TRUE);
16135         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16136         ClearPremoveHighlights();
16137         CommentPopDown();
16138         ToNrEvent(currentMove+1);
16139 }
16140