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, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53 
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57 
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61 
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67 
68 #else
69 
70 #   include <sys/file.h>
71 #   define SLASH '/'
72 
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84 
85 #endif
86 
87 #include "config.h"
88 
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97 
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109 
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117 
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128 
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136 
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140 
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153 
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168 
169 
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172 			 char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174 		      char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187 		   /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199 			   char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201 			int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208 
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248 
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252 
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255 
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264 
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301 
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309 
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318 
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321 
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327 
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332 
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342 
343 char*
safeStrCpy(char * dst,const char * src,size_t count)344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350 
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356 	fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358 
359   return dst;
360 }
361 
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364 
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
u64ToDouble(u64 value)372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381 
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
PosFlags(index)390 PosFlags (index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:		// [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430 
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433 
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442 
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 
447 ChessProgramState first, second, pairing;
448 
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458 
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461 
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489 
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503 
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510 
511 GameInfo gameInfo;
512 
513 AppData appData;
514 
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 signed char  initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525 
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535 
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541 
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 	WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546 	BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548 
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551 	WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555 
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562 
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569 
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574 	BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576 
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583 
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590 
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597 
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600 	WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602 	BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604 
605 
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613 
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620 
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627 
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634 
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641 
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648 
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655 
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666 
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677 
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684 
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710 
711 
712 Board initialPosition;
713 
714 
715 /* Convert str to a rating. Checks for special cases of "----",
716 
717    "++++", etc. Also strips ()'s */
718 int
string_to_rating(char * str)719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;	/* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727 
728 void
ClearProgramStats()729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743 
744 void
CommonEngineInit()745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748 	first.twoMachinesColor = "black\n";
749 	second.twoMachinesColor = "white\n";
750     } else {
751 	first.twoMachinesColor = "white\n";
752 	second.twoMachinesColor = "black\n";
753     }
754 
755     first.other = &second;
756     second.other = &first;
757 
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766 
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769 	programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770 	sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777 
778 void
UnloadEngine(ChessProgramState * cps)779 UnloadEngine (ChessProgramState *cps)
780 {
781 	/* Kill off first chess program */
782 	if (cps->isr != NULL)
783 	  RemoveInputSource(cps->isr);
784 	cps->isr = NULL;
785 
786 	if (cps->pr != NoProc) {
787 	    ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789 	    SendToProgram("quit\n", cps);
790 	    DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791 	}
792 	cps->pr = NoProc;
793 	if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795 
796 void
ClearOptions(ChessProgramState * cps)797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802 	cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803 	cps->option[i].textValue = 0;
804     }
805 }
806 
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815 
816 void
InitEngine(ChessProgramState * cps,int n)817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819 
820     ClearOptions(cps);
821 
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828 
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859 
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865 
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868 
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872 
873     /* [HGM] debug */
874     cps->debug = FALSE;
875 
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881 
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884 
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889 
890     if (appData.protocolVersion[n] > PROTOVER
891 	|| appData.protocolVersion[n] < 1)
892       {
893 	char buf[MSG_SIZ];
894 	int len;
895 
896 	len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897 		       appData.protocolVersion[n]);
898 	if( (len >= MSG_SIZ) && appData.debugMode )
899 	  fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900 
901 	DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905 	cps->protocolVersion = appData.protocolVersion[n];
906       }
907 
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911 
912 ChessProgramState *savCps;
913 
914 GameMode oldMode;
915 
916 void
LoadEngine()917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923 	// we changed variant when loading the engine; this forces us to reset
924 	Reset(TRUE, savCps != &first);
925 	oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936 
937 void
ReplaceEngine(ChessProgramState * cps,int n)938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953 
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956 
957 static char resetOptions[] =
958 	"-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959 	"-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960 	"-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961 	"-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962 
963 void
FloatToFront(char ** list,char * engineLine)964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973 	q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974 	while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981 
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983 
984 void
Load(ChessProgramState * cps,int i)985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989 	snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990 	SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991 	ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992 	FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993 	appData.firstProtocolVersion = PROTOVER;
994 	ParseArgsFromString(buf);
995 	SwapEngines(i);
996 	ReplaceEngine(cps, i);
997 	FloatToFront(&appData.recentEngineList, engineLine);
998 	return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004 	ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006 	p[-1] = 0;
1007 	ASSIGN(appData.directory[i], engineName);
1008 	p[-1] = SLASH;
1009 	if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013 	if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014 	snprintf(command, MSG_SIZ, "%s %s", p, params);
1015 	p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025 	int len;
1026 	char quote;
1027 	q = firstChessProgramNames;
1028 	if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029 	quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030 	snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031 			quote, p, quote, appData.directory[i],
1032 			useNick ? " -fn \"" : "",
1033 			useNick ? nickName : "",
1034 			useNick ? "\"" : "",
1035 			v1 ? " -firstProtocolVersion 1" : "",
1036 			hasBook ? "" : " -fNoOwnBookUCI",
1037 			isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038 			storeVariant ? " -variant " : "",
1039 			storeVariant ? VariantName(gameInfo.variant) : "");
1040 	if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041 	firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042 	if(insert != q) insert[-1] = NULLCHAR;
1043 	snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044 	if(q) 	free(q);
1045 	FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049 
1050 void
InitTimeControls()1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058 			  appData.movesPerSession)) {
1059 	char buf[MSG_SIZ];
1060 	snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061 	DisplayFatalError(buf, 0, 2);
1062     }
1063 
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068 	matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069 	if (matched == 1) {
1070 	    searchTime = min * 60;
1071 	} else if (matched == 2) {
1072 	    searchTime = min * 60 + sec;
1073 	} else {
1074 	    char buf[MSG_SIZ];
1075 	    snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076 	    DisplayFatalError(buf, 0, 2);
1077 	}
1078     }
1079 }
1080 
1081 void
InitBackEnd1()1082 InitBackEnd1 ()
1083 {
1084 
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087 
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092 
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096 
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101 
1102 
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107 	appData.matchMode = FALSE;
1108 	appData.matchGames = 0;
1109 #if ZIPPY
1110 	appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112 	appData.zippyPlay = FALSE;
1113 	appData.zippyTalk = FALSE;
1114 	appData.noChessProgram = TRUE;
1115 #endif
1116 	if (*appData.icsHelper != NULLCHAR) {
1117 	    appData.useTelnet = TRUE;
1118 	    appData.telnetProgram = appData.icsHelper;
1119 	}
1120     } else {
1121 	appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123 
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127 
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134 
1135     InitTimeControls();
1136 
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139 
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143 
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150 
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154 	appData.clockMode = FALSE;
1155 	first.sendTime = second.sendTime = 0;
1156     }
1157 
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167 
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171 
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180 	/* case VariantFischeRandom: (Fabien: moved below) */
1181 	len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182 	if( (len >= MSG_SIZ) && appData.debugMode )
1183 	  fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184 
1185 	DisplayFatalError(buf, 0, 2);
1186 	return;
1187 
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199 	len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200 	if( (len >= MSG_SIZ) && appData.debugMode )
1201 	  fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202 
1203 	DisplayFatalError(buf, 0, 2);
1204 	return;
1205 
1206       case VariantNormal:     /* definitely works! */
1207 	if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208 	  safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209 	  return;
1210 	}
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222 			         offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227 			         and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229 			         and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231 			         and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248 	break;
1249       }
1250     }
1251 
1252 }
1253 
1254 int
NextIntegerFromString(char ** str,long * value)1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259 
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263 
1264     *value = 0;
1265 
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271 
1272         result = 0;
1273     }
1274 
1275     *str = s;
1276 
1277     return result;
1278 }
1279 
1280 int
NextTimeControlFromString(char ** str,long * value)1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285 
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294 
1295     return result;
1296 }
1297 
1298 int
NextSessionFromString(char ** str,int * moves,long * tc,long * inc,int * incType)1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302 
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308 
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327 
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330 
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339 
1340 int
GetTimeQuota(int movenr,int lastUsed,char * tcString)1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346 
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357 
1358     return 0; // no new time quota on this move
1359 }
1360 
1361 int
ParseTimeControl(char * tc,float ti,int mps)1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368 
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373 
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385 
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389 
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393 
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397 
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401 
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407 
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411 
1412   timeControl = tc1 * 1000;
1413 
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423 
1424 void
InitBackEnd2()1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435 
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438 	appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440 	appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443 	appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445 	if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446 	if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458 
1459 int
CalculateIndex(int index,int gameNr)1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469 
1470 int
LoadGameOrPosition(int gameNr)1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474 	if (!LoadGameFromFile(appData.loadGameFile,
1475 		CalculateIndex(appData.loadGameIndex, gameNr),
1476 			      appData.loadGameFile, FALSE)) {
1477 	    DisplayFatalError(_("Bad game file"), 0, 1);
1478 	    return 0;
1479 	}
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481 	if (!LoadPositionFromFile(appData.loadPositionFile,
1482 		CalculateIndex(appData.loadPositionIndex, gameNr),
1483 				  appData.loadPositionFile)) {
1484 	    DisplayFatalError(_("Bad position file"), 0, 1);
1485 	    return 0;
1486 	}
1487     }
1488     return 1;
1489 }
1490 
1491 void
ReserveGame(int gameNr,char resChar)1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515 	if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516 	q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521 	 fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530 	 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531 	UnloadEngine(&first);  // next game belongs to other pairing;
1532 	UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536 
1537 void
MatchEvent(int mode)1538 MatchEvent (int mode)
1539 {	// [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540 	int dummy;
1541 	if(matchMode) { // already in match mode: switch it off
1542 	    abortMatch = TRUE;
1543 	    if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544 	    return;
1545 	}
1546 //	if(gameMode != BeginningOfGame) {
1547 //	    DisplayError(_("You can only start a match from the initial position."), 0);
1548 //	    return;
1549 //	}
1550 	abortMatch = FALSE;
1551 	if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552 	/* Set up machine vs. machine match */
1553 	nextGame = 0;
1554 	NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555 	if(appData.tourneyFile[0]) {
1556 	    ReserveGame(-1, 0);
1557 	    if(nextGame > appData.matchGames) {
1558 		char buf[MSG_SIZ];
1559 		if(strchr(appData.results, '*') == NULL) {
1560 		    FILE *f;
1561 		    appData.tourneyCycles++;
1562 		    if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563 			fclose(f);
1564 			NextTourneyGame(-1, &dummy);
1565 			ReserveGame(-1, 0);
1566 			if(nextGame <= appData.matchGames) {
1567 			    DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568 			    matchMode = mode;
1569 			    ScheduleDelayedEvent(NextMatchGame, 10000);
1570 			    return;
1571 			}
1572 		    }
1573 		}
1574 		snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575 		DisplayError(buf, 0);
1576 		appData.tourneyFile[0] = 0;
1577 		return;
1578 	    }
1579 	} else
1580 	if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581 	    DisplayFatalError(_("Can't have a match with no chess programs"),
1582 			      0, 2);
1583 	    return;
1584 	}
1585 	matchMode = mode;
1586 	matchGame = roundNr = 1;
1587 	first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588 	NextMatchGame();
1589 }
1590 
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592 
1593 void
InitBackEnd3(void)1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599 
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605 	char c, *q = first.variants, *p = strchr(q, ',');
1606 	if(p) *p = NULLCHAR;
1607 	if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608 	    int w, h, s;
1609 	    if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610 		appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611 	    ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612 	    Reset(TRUE, FALSE);         // and re-initialize
1613 	}
1614 	if(p) *p = ',';
1615     }
1616 
1617     InitChessProgram(&first, startedFromSetupPosition);
1618 
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620 	free(programVersion);
1621 	programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622 	sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623 	FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625 
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631 	err = establish();
1632 	if (err != 0)
1633 	  {
1634 	    if (*appData.icsCommPort != NULLCHAR)
1635 	      len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636 			     appData.icsCommPort);
1637 	    else
1638 	      len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639 			appData.icsHost, appData.icsPort);
1640 
1641 	    if( (len >= MSG_SIZ) && appData.debugMode )
1642 	      fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643 
1644 	    DisplayFatalError(buf, err, 1);
1645 	    return;
1646 	}
1647 	SetICSMode();
1648 	telnetISR =
1649 	  AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650 	fromUserISR =
1651 	  AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652 	if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653 	    ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655 	SetNCPMode();
1656     } else {
1657 	SetGNUMode();
1658     }
1659 
1660     if (*appData.cmailGameName != NULLCHAR) {
1661 	SetCmailMode();
1662 	OpenLoopback(&cmailPR);
1663 	cmailISR =
1664 	  AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666 
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696 	fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697 
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701 
1702     if (appData.matchMode) {
1703 	if(appData.tourneyFile[0]) { // start tourney from command line
1704 	    FILE *f;
1705 	    if(f = fopen(appData.tourneyFile, "r")) {
1706 		ParseArgsFromFile(f); // make sure tourney parmeters re known
1707 		fclose(f);
1708 		appData.clockMode = TRUE;
1709 		SetGNUMode();
1710 	    } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711 	}
1712 	MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714 	/* Set up cmail mode */
1715 	ReloadCmailMsgEvent(TRUE);
1716     } else {
1717 	/* Set up other modes */
1718 	if (initialMode == AnalyzeFile) {
1719 	  if (*appData.loadGameFile == NULLCHAR) {
1720 	    DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721 	    return;
1722 	  }
1723 	}
1724 	if (*appData.loadGameFile != NULLCHAR) {
1725 	    (void) LoadGameFromFile(appData.loadGameFile,
1726 				    appData.loadGameIndex,
1727 				    appData.loadGameFile, TRUE);
1728 	} else if (*appData.loadPositionFile != NULLCHAR) {
1729 	    (void) LoadPositionFromFile(appData.loadPositionFile,
1730 					appData.loadPositionIndex,
1731 					appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743             }
1744 	}
1745 	if (initialMode == AnalyzeMode) {
1746 	  if (appData.noChessProgram) {
1747 	    DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1748 	    return;
1749 	  }
1750 	  if (appData.icsActive) {
1751 	    DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1752 	    return;
1753 	  }
1754 	  AnalyzeModeEvent();
1755 	} else if (initialMode == AnalyzeFile) {
1756 	  appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1757 	  ShowThinkingEvent();
1758 	  AnalyzeFileEvent();
1759 	  AnalysisPeriodicEvent(1);
1760 	} else if (initialMode == MachinePlaysWhite) {
1761 	  if (appData.noChessProgram) {
1762 	    DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1763 			      0, 2);
1764 	    return;
1765 	  }
1766 	  if (appData.icsActive) {
1767 	    DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1768 			      0, 2);
1769 	    return;
1770 	  }
1771 	  MachineWhiteEvent();
1772 	} else if (initialMode == MachinePlaysBlack) {
1773 	  if (appData.noChessProgram) {
1774 	    DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1775 			      0, 2);
1776 	    return;
1777 	  }
1778 	  if (appData.icsActive) {
1779 	    DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1780 			      0, 2);
1781 	    return;
1782 	  }
1783 	  MachineBlackEvent();
1784 	} else if (initialMode == TwoMachinesPlay) {
1785 	  if (appData.noChessProgram) {
1786 	    DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1787 			      0, 2);
1788 	    return;
1789 	  }
1790 	  if (appData.icsActive) {
1791 	    DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1792 			      0, 2);
1793 	    return;
1794 	  }
1795 	  TwoMachinesEvent();
1796 	} else if (initialMode == EditGame) {
1797 	  EditGameEvent();
1798 	} else if (initialMode == EditPosition) {
1799 	  EditPositionEvent();
1800 	} else if (initialMode == Training) {
1801 	  if (*appData.loadGameFile == NULLCHAR) {
1802 	    DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1803 	    return;
1804 	  }
1805 	  TrainingEvent();
1806 	}
1807     }
1808 }
1809 
1810 void
HistorySet(char movelist[][2* MOVE_LEN],int first,int last,int current)1811 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1812 {
1813     DisplayBook(current+1);
1814 
1815     MoveHistorySet( movelist, first, last, current, pvInfoList );
1816 
1817     EvalGraphSet( first, last, current, pvInfoList );
1818 
1819     MakeEngineOutputTitle();
1820 }
1821 
1822 /*
1823  * Establish will establish a contact to a remote host.port.
1824  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1825  *  used to talk to the host.
1826  * Returns 0 if okay, error code if not.
1827  */
1828 int
establish()1829 establish ()
1830 {
1831     char buf[MSG_SIZ];
1832 
1833     if (*appData.icsCommPort != NULLCHAR) {
1834 	/* Talk to the host through a serial comm port */
1835 	return OpenCommPort(appData.icsCommPort, &icsPR);
1836 
1837     } else if (*appData.gateway != NULLCHAR) {
1838 	if (*appData.remoteShell == NULLCHAR) {
1839 	    /* Use the rcmd protocol to run telnet program on a gateway host */
1840 	    snprintf(buf, sizeof(buf), "%s %s %s",
1841 		    appData.telnetProgram, appData.icsHost, appData.icsPort);
1842 	    return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1843 
1844 	} else {
1845 	    /* Use the rsh program to run telnet program on a gateway host */
1846 	    if (*appData.remoteUser == NULLCHAR) {
1847 		snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1848 			appData.gateway, appData.telnetProgram,
1849 			appData.icsHost, appData.icsPort);
1850 	    } else {
1851 		snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1852 			appData.remoteShell, appData.gateway,
1853 			appData.remoteUser, appData.telnetProgram,
1854 			appData.icsHost, appData.icsPort);
1855 	    }
1856 	    return StartChildProcess(buf, "", &icsPR);
1857 
1858 	}
1859     } else if (appData.useTelnet) {
1860 	return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1861 
1862     } else {
1863 	/* TCP socket interface differs somewhat between
1864 	   Unix and NT; handle details in the front end.
1865 	   */
1866 	return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1867     }
1868 }
1869 
1870 void
EscapeExpand(char * p,char * q)1871 EscapeExpand (char *p, char *q)
1872 {	// [HGM] initstring: routine to shape up string arguments
1873 	while(*p++ = *q++) if(p[-1] == '\\')
1874 	    switch(*q++) {
1875 		case 'n': p[-1] = '\n'; break;
1876 		case 'r': p[-1] = '\r'; break;
1877 		case 't': p[-1] = '\t'; break;
1878 		case '\\': p[-1] = '\\'; break;
1879 		case 0: *p = 0; return;
1880 		default: p[-1] = q[-1]; break;
1881 	    }
1882 }
1883 
1884 void
show_bytes(FILE * fp,char * buf,int count)1885 show_bytes (FILE *fp, char *buf, int count)
1886 {
1887     while (count--) {
1888 	if (*buf < 040 || *(unsigned char *) buf > 0177) {
1889 	    fprintf(fp, "\\%03o", *buf & 0xff);
1890 	} else {
1891 	    putc(*buf, fp);
1892 	}
1893 	buf++;
1894     }
1895     fflush(fp);
1896 }
1897 
1898 /* Returns an errno value */
1899 int
OutputMaybeTelnet(ProcRef pr,char * message,int count,int * outError)1900 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1901 {
1902     char buf[8192], *p, *q, *buflim;
1903     int left, newcount, outcount;
1904 
1905     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1906 	*appData.gateway != NULLCHAR) {
1907 	if (appData.debugMode) {
1908 	    fprintf(debugFP, ">ICS: ");
1909 	    show_bytes(debugFP, message, count);
1910 	    fprintf(debugFP, "\n");
1911 	}
1912 	return OutputToProcess(pr, message, count, outError);
1913     }
1914 
1915     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1916     p = message;
1917     q = buf;
1918     left = count;
1919     newcount = 0;
1920     while (left) {
1921 	if (q >= buflim) {
1922 	    if (appData.debugMode) {
1923 		fprintf(debugFP, ">ICS: ");
1924 		show_bytes(debugFP, buf, newcount);
1925 		fprintf(debugFP, "\n");
1926 	    }
1927 	    outcount = OutputToProcess(pr, buf, newcount, outError);
1928 	    if (outcount < newcount) return -1; /* to be sure */
1929 	    q = buf;
1930 	    newcount = 0;
1931 	}
1932 	if (*p == '\n') {
1933 	    *q++ = '\r';
1934 	    newcount++;
1935 	} else if (((unsigned char) *p) == TN_IAC) {
1936 	    *q++ = (char) TN_IAC;
1937 	    newcount ++;
1938 	}
1939 	*q++ = *p++;
1940 	newcount++;
1941 	left--;
1942     }
1943     if (appData.debugMode) {
1944 	fprintf(debugFP, ">ICS: ");
1945 	show_bytes(debugFP, buf, newcount);
1946 	fprintf(debugFP, "\n");
1947     }
1948     outcount = OutputToProcess(pr, buf, newcount, outError);
1949     if (outcount < newcount) return -1; /* to be sure */
1950     return count;
1951 }
1952 
1953 void
read_from_player(InputSourceRef isr,VOIDSTAR closure,char * message,int count,int error)1954 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1955 {
1956     int outError, outCount;
1957     static int gotEof = 0;
1958     static FILE *ini;
1959 
1960     /* Pass data read from player on to ICS */
1961     if (count > 0) {
1962 	gotEof = 0;
1963 	outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1964 	if (outCount < count) {
1965             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1966 	}
1967 	if(have_sent_ICS_logon == 2) {
1968 	  if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1969 	    fprintf(ini, "%s", message);
1970 	    have_sent_ICS_logon = 3;
1971 	  } else
1972 	    have_sent_ICS_logon = 1;
1973 	} else if(have_sent_ICS_logon == 3) {
1974 	    fprintf(ini, "%s", message);
1975 	    fclose(ini);
1976 	  have_sent_ICS_logon = 1;
1977 	}
1978     } else if (count < 0) {
1979 	RemoveInputSource(isr);
1980 	DisplayFatalError(_("Error reading from keyboard"), error, 1);
1981     } else if (gotEof++ > 0) {
1982 	RemoveInputSource(isr);
1983 	DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1984     }
1985 }
1986 
1987 void
KeepAlive()1988 KeepAlive ()
1989 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1990     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1991     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1992     SendToICS("date\n");
1993     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1994 }
1995 
1996 /* added routine for printf style output to ics */
1997 void
ics_printf(char * format,...)1998 ics_printf (char *format, ...)
1999 {
2000     char buffer[MSG_SIZ];
2001     va_list args;
2002 
2003     va_start(args, format);
2004     vsnprintf(buffer, sizeof(buffer), format, args);
2005     buffer[sizeof(buffer)-1] = '\0';
2006     SendToICS(buffer);
2007     va_end(args);
2008 }
2009 
2010 void
SendToICS(char * s)2011 SendToICS (char *s)
2012 {
2013     int count, outCount, outError;
2014 
2015     if (icsPR == NoProc) return;
2016 
2017     count = strlen(s);
2018     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2019     if (outCount < count) {
2020 	DisplayFatalError(_("Error writing to ICS"), outError, 1);
2021     }
2022 }
2023 
2024 /* This is used for sending logon scripts to the ICS. Sending
2025    without a delay causes problems when using timestamp on ICC
2026    (at least on my machine). */
2027 void
SendToICSDelayed(char * s,long msdelay)2028 SendToICSDelayed (char *s, long msdelay)
2029 {
2030     int count, outCount, outError;
2031 
2032     if (icsPR == NoProc) return;
2033 
2034     count = strlen(s);
2035     if (appData.debugMode) {
2036 	fprintf(debugFP, ">ICS: ");
2037 	show_bytes(debugFP, s, count);
2038 	fprintf(debugFP, "\n");
2039     }
2040     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2041 				      msdelay);
2042     if (outCount < count) {
2043 	DisplayFatalError(_("Error writing to ICS"), outError, 1);
2044     }
2045 }
2046 
2047 
2048 /* Remove all highlighting escape sequences in s
2049    Also deletes any suffix starting with '('
2050    */
2051 char *
StripHighlightAndTitle(char * s)2052 StripHighlightAndTitle (char *s)
2053 {
2054     static char retbuf[MSG_SIZ];
2055     char *p = retbuf;
2056 
2057     while (*s != NULLCHAR) {
2058 	while (*s == '\033') {
2059 	    while (*s != NULLCHAR && !isalpha(*s)) s++;
2060 	    if (*s != NULLCHAR) s++;
2061 	}
2062 	while (*s != NULLCHAR && *s != '\033') {
2063 	    if (*s == '(' || *s == '[') {
2064 		*p = NULLCHAR;
2065 		return retbuf;
2066 	    }
2067 	    *p++ = *s++;
2068 	}
2069     }
2070     *p = NULLCHAR;
2071     return retbuf;
2072 }
2073 
2074 /* Remove all highlighting escape sequences in s */
2075 char *
StripHighlight(char * s)2076 StripHighlight (char *s)
2077 {
2078     static char retbuf[MSG_SIZ];
2079     char *p = retbuf;
2080 
2081     while (*s != NULLCHAR) {
2082 	while (*s == '\033') {
2083 	    while (*s != NULLCHAR && !isalpha(*s)) s++;
2084 	    if (*s != NULLCHAR) s++;
2085 	}
2086 	while (*s != NULLCHAR && *s != '\033') {
2087 	    *p++ = *s++;
2088 	}
2089     }
2090     *p = NULLCHAR;
2091     return retbuf;
2092 }
2093 
2094 char engineVariant[MSG_SIZ];
2095 char *variantNames[] = VARIANT_NAMES;
2096 char *
VariantName(VariantClass v)2097 VariantName (VariantClass v)
2098 {
2099     if(v == VariantUnknown || *engineVariant) return engineVariant;
2100     return variantNames[v];
2101 }
2102 
2103 
2104 /* Identify a variant from the strings the chess servers use or the
2105    PGN Variant tag names we use. */
2106 VariantClass
StringToVariant(char * e)2107 StringToVariant (char *e)
2108 {
2109     char *p;
2110     int wnum = -1;
2111     VariantClass v = VariantNormal;
2112     int i, found = FALSE;
2113     char buf[MSG_SIZ], c;
2114     int len;
2115 
2116     if (!e) return v;
2117 
2118     /* [HGM] skip over optional board-size prefixes */
2119     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2120         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2121         while( *e++ != '_');
2122     }
2123 
2124     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2125 	v = VariantNormal;
2126 	found = TRUE;
2127     } else
2128     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2129       if (p = StrCaseStr(e, variantNames[i])) {
2130 	if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2131 	v = (VariantClass) i;
2132 	found = TRUE;
2133 	break;
2134       }
2135     }
2136 
2137     if (!found) {
2138       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2139 	  || StrCaseStr(e, "wild/fr")
2140 	  || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2141         v = VariantFischeRandom;
2142       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2143 		 (i = 1, p = StrCaseStr(e, "w"))) {
2144 	p += i;
2145 	while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2146 	if (isdigit(*p)) {
2147 	  wnum = atoi(p);
2148 	} else {
2149 	  wnum = -1;
2150 	}
2151 	switch (wnum) {
2152 	case 0: /* FICS only, actually */
2153 	case 1:
2154 	  /* Castling legal even if K starts on d-file */
2155 	  v = VariantWildCastle;
2156 	  break;
2157 	case 2:
2158 	case 3:
2159 	case 4:
2160 	  /* Castling illegal even if K & R happen to start in
2161 	     normal positions. */
2162 	  v = VariantNoCastle;
2163 	  break;
2164 	case 5:
2165 	case 7:
2166 	case 8:
2167 	case 10:
2168 	case 11:
2169 	case 12:
2170 	case 13:
2171 	case 14:
2172 	case 15:
2173 	case 18:
2174 	case 19:
2175 	  /* Castling legal iff K & R start in normal positions */
2176 	  v = VariantNormal;
2177 	  break;
2178 	case 6:
2179 	case 20:
2180 	case 21:
2181 	  /* Special wilds for position setup; unclear what to do here */
2182 	  v = VariantLoadable;
2183 	  break;
2184 	case 9:
2185 	  /* Bizarre ICC game */
2186 	  v = VariantTwoKings;
2187 	  break;
2188 	case 16:
2189 	  v = VariantKriegspiel;
2190 	  break;
2191 	case 17:
2192 	  v = VariantLosers;
2193 	  break;
2194 	case 22:
2195 	  v = VariantFischeRandom;
2196 	  break;
2197 	case 23:
2198 	  v = VariantCrazyhouse;
2199 	  break;
2200 	case 24:
2201 	  v = VariantBughouse;
2202 	  break;
2203 	case 25:
2204 	  v = Variant3Check;
2205 	  break;
2206 	case 26:
2207 	  /* Not quite the same as FICS suicide! */
2208 	  v = VariantGiveaway;
2209 	  break;
2210 	case 27:
2211 	  v = VariantAtomic;
2212 	  break;
2213 	case 28:
2214 	  v = VariantShatranj;
2215 	  break;
2216 
2217 	/* Temporary names for future ICC types.  The name *will* change in
2218 	   the next xboard/WinBoard release after ICC defines it. */
2219 	case 29:
2220 	  v = Variant29;
2221 	  break;
2222 	case 30:
2223 	  v = Variant30;
2224 	  break;
2225 	case 31:
2226 	  v = Variant31;
2227 	  break;
2228 	case 32:
2229 	  v = Variant32;
2230 	  break;
2231 	case 33:
2232 	  v = Variant33;
2233 	  break;
2234 	case 34:
2235 	  v = Variant34;
2236 	  break;
2237 	case 35:
2238 	  v = Variant35;
2239 	  break;
2240 	case 36:
2241 	  v = Variant36;
2242 	  break;
2243         case 37:
2244           v = VariantShogi;
2245 	  break;
2246         case 38:
2247           v = VariantXiangqi;
2248 	  break;
2249         case 39:
2250           v = VariantCourier;
2251 	  break;
2252         case 40:
2253           v = VariantGothic;
2254 	  break;
2255         case 41:
2256           v = VariantCapablanca;
2257 	  break;
2258         case 42:
2259           v = VariantKnightmate;
2260 	  break;
2261         case 43:
2262           v = VariantFairy;
2263           break;
2264         case 44:
2265           v = VariantCylinder;
2266 	  break;
2267         case 45:
2268           v = VariantFalcon;
2269 	  break;
2270         case 46:
2271           v = VariantCapaRandom;
2272 	  break;
2273         case 47:
2274           v = VariantBerolina;
2275 	  break;
2276         case 48:
2277           v = VariantJanus;
2278 	  break;
2279         case 49:
2280           v = VariantSuper;
2281 	  break;
2282         case 50:
2283           v = VariantGreat;
2284 	  break;
2285 	case -1:
2286 	  /* Found "wild" or "w" in the string but no number;
2287 	     must assume it's normal chess. */
2288 	  v = VariantNormal;
2289 	  break;
2290 	default:
2291 	  len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2292 	  if( (len >= MSG_SIZ) && appData.debugMode )
2293 	    fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2294 
2295 	  DisplayError(buf, 0);
2296 	  v = VariantUnknown;
2297 	  break;
2298 	}
2299       }
2300     }
2301     if (appData.debugMode) {
2302       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2303 	      e, wnum, VariantName(v));
2304     }
2305     return v;
2306 }
2307 
2308 static int leftover_start = 0, leftover_len = 0;
2309 char star_match[STAR_MATCH_N][MSG_SIZ];
2310 
2311 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2312    advance *index beyond it, and set leftover_start to the new value of
2313    *index; else return FALSE.  If pattern contains the character '*', it
2314    matches any sequence of characters not containing '\r', '\n', or the
2315    character following the '*' (if any), and the matched sequence(s) are
2316    copied into star_match.
2317    */
2318 int
looking_at(char * buf,int * index,char * pattern)2319 looking_at ( char *buf, int *index, char *pattern)
2320 {
2321     char *bufp = &buf[*index], *patternp = pattern;
2322     int star_count = 0;
2323     char *matchp = star_match[0];
2324 
2325     for (;;) {
2326 	if (*patternp == NULLCHAR) {
2327 	    *index = leftover_start = bufp - buf;
2328 	    *matchp = NULLCHAR;
2329 	    return TRUE;
2330 	}
2331 	if (*bufp == NULLCHAR) return FALSE;
2332 	if (*patternp == '*') {
2333 	    if (*bufp == *(patternp + 1)) {
2334 		*matchp = NULLCHAR;
2335 		matchp = star_match[++star_count];
2336 		patternp += 2;
2337 		bufp++;
2338 		continue;
2339 	    } else if (*bufp == '\n' || *bufp == '\r') {
2340 		patternp++;
2341 		if (*patternp == NULLCHAR)
2342 		  continue;
2343 		else
2344 		  return FALSE;
2345 	    } else {
2346 		*matchp++ = *bufp++;
2347 		continue;
2348 	    }
2349 	}
2350 	if (*patternp != *bufp) return FALSE;
2351 	patternp++;
2352 	bufp++;
2353     }
2354 }
2355 
2356 void
SendToPlayer(char * data,int length)2357 SendToPlayer (char *data, int length)
2358 {
2359     int error, outCount;
2360     outCount = OutputToProcess(NoProc, data, length, &error);
2361     if (outCount < length) {
2362 	DisplayFatalError(_("Error writing to display"), error, 1);
2363     }
2364 }
2365 
2366 void
PackHolding(char packed[],char * holding)2367 PackHolding (char packed[], char *holding)
2368 {
2369     char *p = holding;
2370     char *q = packed;
2371     int runlength = 0;
2372     int curr = 9999;
2373     do {
2374 	if (*p == curr) {
2375 	    runlength++;
2376 	} else {
2377 	    switch (runlength) {
2378 	      case 0:
2379 		break;
2380 	      case 1:
2381 		*q++ = curr;
2382 		break;
2383 	      case 2:
2384 		*q++ = curr;
2385 		*q++ = curr;
2386 		break;
2387 	      default:
2388 		sprintf(q, "%d", runlength);
2389 		while (*q) q++;
2390 		*q++ = curr;
2391 		break;
2392 	    }
2393 	    runlength = 1;
2394 	    curr = *p;
2395 	}
2396     } while (*p++);
2397     *q = NULLCHAR;
2398 }
2399 
2400 /* Telnet protocol requests from the front end */
2401 void
TelnetRequest(unsigned char ddww,unsigned char option)2402 TelnetRequest (unsigned char ddww, unsigned char option)
2403 {
2404     unsigned char msg[3];
2405     int outCount, outError;
2406 
2407     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2408 
2409     if (appData.debugMode) {
2410 	char buf1[8], buf2[8], *ddwwStr, *optionStr;
2411 	switch (ddww) {
2412 	  case TN_DO:
2413 	    ddwwStr = "DO";
2414 	    break;
2415 	  case TN_DONT:
2416 	    ddwwStr = "DONT";
2417 	    break;
2418 	  case TN_WILL:
2419 	    ddwwStr = "WILL";
2420 	    break;
2421 	  case TN_WONT:
2422 	    ddwwStr = "WONT";
2423 	    break;
2424 	  default:
2425 	    ddwwStr = buf1;
2426 	    snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2427 	    break;
2428 	}
2429 	switch (option) {
2430 	  case TN_ECHO:
2431 	    optionStr = "ECHO";
2432 	    break;
2433 	  default:
2434 	    optionStr = buf2;
2435 	    snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2436 	    break;
2437 	}
2438 	fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2439     }
2440     msg[0] = TN_IAC;
2441     msg[1] = ddww;
2442     msg[2] = option;
2443     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2444     if (outCount < 3) {
2445 	DisplayFatalError(_("Error writing to ICS"), outError, 1);
2446     }
2447 }
2448 
2449 void
DoEcho()2450 DoEcho ()
2451 {
2452     if (!appData.icsActive) return;
2453     TelnetRequest(TN_DO, TN_ECHO);
2454 }
2455 
2456 void
DontEcho()2457 DontEcho ()
2458 {
2459     if (!appData.icsActive) return;
2460     TelnetRequest(TN_DONT, TN_ECHO);
2461 }
2462 
2463 void
CopyHoldings(Board board,char * holdings,ChessSquare lowestPiece)2464 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2465 {
2466     /* put the holdings sent to us by the server on the board holdings area */
2467     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2468     char p;
2469     ChessSquare piece;
2470 
2471     if(gameInfo.holdingsWidth < 2)  return;
2472     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2473 	return; // prevent overwriting by pre-board holdings
2474 
2475     if( (int)lowestPiece >= BlackPawn ) {
2476         holdingsColumn = 0;
2477         countsColumn = 1;
2478         holdingsStartRow = BOARD_HEIGHT-1;
2479         direction = -1;
2480     } else {
2481         holdingsColumn = BOARD_WIDTH-1;
2482         countsColumn = BOARD_WIDTH-2;
2483         holdingsStartRow = 0;
2484         direction = 1;
2485     }
2486 
2487     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2488         board[i][holdingsColumn] = EmptySquare;
2489         board[i][countsColumn]   = (ChessSquare) 0;
2490     }
2491     while( (p=*holdings++) != NULLCHAR ) {
2492         piece = CharToPiece( ToUpper(p) );
2493         if(piece == EmptySquare) continue;
2494         /*j = (int) piece - (int) WhitePawn;*/
2495         j = PieceToNumber(piece);
2496         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2497         if(j < 0) continue;               /* should not happen */
2498         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2499         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2500         board[holdingsStartRow+j*direction][countsColumn]++;
2501     }
2502 }
2503 
2504 
2505 void
VariantSwitch(Board board,VariantClass newVariant)2506 VariantSwitch (Board board, VariantClass newVariant)
2507 {
2508    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2509    static Board oldBoard;
2510 
2511    startedFromPositionFile = FALSE;
2512    if(gameInfo.variant == newVariant) return;
2513 
2514    /* [HGM] This routine is called each time an assignment is made to
2515     * gameInfo.variant during a game, to make sure the board sizes
2516     * are set to match the new variant. If that means adding or deleting
2517     * holdings, we shift the playing board accordingly
2518     * This kludge is needed because in ICS observe mode, we get boards
2519     * of an ongoing game without knowing the variant, and learn about the
2520     * latter only later. This can be because of the move list we requested,
2521     * in which case the game history is refilled from the beginning anyway,
2522     * but also when receiving holdings of a crazyhouse game. In the latter
2523     * case we want to add those holdings to the already received position.
2524     */
2525 
2526 
2527    if (appData.debugMode) {
2528      fprintf(debugFP, "Switch board from %s to %s\n",
2529 	     VariantName(gameInfo.variant), VariantName(newVariant));
2530      setbuf(debugFP, NULL);
2531    }
2532    shuffleOpenings = 0;       /* [HGM] shuffle */
2533    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2534    switch(newVariant)
2535      {
2536      case VariantShogi:
2537        newWidth = 9;  newHeight = 9;
2538        gameInfo.holdingsSize = 7;
2539      case VariantBughouse:
2540      case VariantCrazyhouse:
2541        newHoldingsWidth = 2; break;
2542      case VariantGreat:
2543        newWidth = 10;
2544      case VariantSuper:
2545        newHoldingsWidth = 2;
2546        gameInfo.holdingsSize = 8;
2547        break;
2548      case VariantGothic:
2549      case VariantCapablanca:
2550      case VariantCapaRandom:
2551        newWidth = 10;
2552      default:
2553        newHoldingsWidth = gameInfo.holdingsSize = 0;
2554      };
2555 
2556    if(newWidth  != gameInfo.boardWidth  ||
2557       newHeight != gameInfo.boardHeight ||
2558       newHoldingsWidth != gameInfo.holdingsWidth ) {
2559 
2560      /* shift position to new playing area, if needed */
2561      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2562        for(i=0; i<BOARD_HEIGHT; i++)
2563 	 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2564 	   board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2565 	     board[i][j];
2566        for(i=0; i<newHeight; i++) {
2567 	 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2568 	 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2569        }
2570      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2571        for(i=0; i<BOARD_HEIGHT; i++)
2572 	 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2573 	   board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2574 	     board[i][j];
2575      }
2576      board[HOLDINGS_SET] = 0;
2577      gameInfo.boardWidth  = newWidth;
2578      gameInfo.boardHeight = newHeight;
2579      gameInfo.holdingsWidth = newHoldingsWidth;
2580      gameInfo.variant = newVariant;
2581      InitDrawingSizes(-2, 0);
2582    } else gameInfo.variant = newVariant;
2583    CopyBoard(oldBoard, board);   // remember correctly formatted board
2584      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2585    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2586 }
2587 
2588 static int loggedOn = FALSE;
2589 
2590 /*-- Game start info cache: --*/
2591 int gs_gamenum;
2592 char gs_kind[MSG_SIZ];
2593 static char player1Name[128] = "";
2594 static char player2Name[128] = "";
2595 static char cont_seq[] = "\n\\   ";
2596 static int player1Rating = -1;
2597 static int player2Rating = -1;
2598 /*----------------------------*/
2599 
2600 ColorClass curColor = ColorNormal;
2601 int suppressKibitz = 0;
2602 
2603 // [HGM] seekgraph
2604 Boolean soughtPending = FALSE;
2605 Boolean seekGraphUp;
2606 #define MAX_SEEK_ADS 200
2607 #define SQUARE 0x80
2608 char *seekAdList[MAX_SEEK_ADS];
2609 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2610 float tcList[MAX_SEEK_ADS];
2611 char colorList[MAX_SEEK_ADS];
2612 int nrOfSeekAds = 0;
2613 int minRating = 1010, maxRating = 2800;
2614 int hMargin = 10, vMargin = 20, h, w;
2615 extern int squareSize, lineGap;
2616 
2617 void
PlotSeekAd(int i)2618 PlotSeekAd (int i)
2619 {
2620 	int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2621 	xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2622 	if(r < minRating+100 && r >=0 ) r = minRating+100;
2623 	if(r > maxRating) r = maxRating;
2624 	if(tc < 1.f) tc = 1.f;
2625 	if(tc > 95.f) tc = 95.f;
2626 	x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2627 	y = ((double)r - minRating)/(maxRating - minRating)
2628 	    * (h-vMargin-squareSize/8-1) + vMargin;
2629 	if(ratingList[i] < 0) y = vMargin + squareSize/4;
2630 	if(strstr(seekAdList[i], " u ")) color = 1;
2631 	if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2632 	   !strstr(seekAdList[i], "bullet") &&
2633 	   !strstr(seekAdList[i], "blitz") &&
2634 	   !strstr(seekAdList[i], "standard") ) color = 2;
2635 	if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2636 	DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2637 }
2638 
2639 void
PlotSingleSeekAd(int i)2640 PlotSingleSeekAd (int i)
2641 {
2642 	PlotSeekAd(i);
2643 }
2644 
2645 void
AddAd(char * handle,char * rating,int base,int inc,char rated,char * type,int nr,Boolean plot)2646 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2647 {
2648 	char buf[MSG_SIZ], *ext = "";
2649 	VariantClass v = StringToVariant(type);
2650 	if(strstr(type, "wild")) {
2651 	    ext = type + 4; // append wild number
2652 	    if(v == VariantFischeRandom) type = "chess960"; else
2653 	    if(v == VariantLoadable) type = "setup"; else
2654 	    type = VariantName(v);
2655 	}
2656 	snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2657 	if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2658 	    if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2659 	    ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2660 	    sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2661 	    tcList[nrOfSeekAds] = base + (2./3.)*inc;
2662 	    seekNrList[nrOfSeekAds] = nr;
2663 	    zList[nrOfSeekAds] = 0;
2664 	    seekAdList[nrOfSeekAds++] = StrSave(buf);
2665 	    if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2666 	}
2667 }
2668 
2669 void
EraseSeekDot(int i)2670 EraseSeekDot (int i)
2671 {
2672     int x = xList[i], y = yList[i], d=squareSize/4, k;
2673     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2674     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2675     // now replot every dot that overlapped
2676     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2677 	int xx = xList[k], yy = yList[k];
2678 	if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2679 	    DrawSeekDot(xx, yy, colorList[k]);
2680     }
2681 }
2682 
2683 void
RemoveSeekAd(int nr)2684 RemoveSeekAd (int nr)
2685 {
2686 	int i;
2687 	for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2688 	    EraseSeekDot(i);
2689 	    if(seekAdList[i]) free(seekAdList[i]);
2690 	    seekAdList[i] = seekAdList[--nrOfSeekAds];
2691 	    seekNrList[i] = seekNrList[nrOfSeekAds];
2692 	    ratingList[i] = ratingList[nrOfSeekAds];
2693 	    colorList[i]  = colorList[nrOfSeekAds];
2694 	    tcList[i] = tcList[nrOfSeekAds];
2695 	    xList[i]  = xList[nrOfSeekAds];
2696 	    yList[i]  = yList[nrOfSeekAds];
2697 	    zList[i]  = zList[nrOfSeekAds];
2698 	    seekAdList[nrOfSeekAds] = NULL;
2699 	    break;
2700 	}
2701 }
2702 
2703 Boolean
MatchSoughtLine(char * line)2704 MatchSoughtLine (char *line)
2705 {
2706     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2707     int nr, base, inc, u=0; char dummy;
2708 
2709     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2710        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2711        (u=1) &&
2712        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2713         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2714 	// match: compact and save the line
2715 	AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2716 	return TRUE;
2717     }
2718     return FALSE;
2719 }
2720 
2721 int
DrawSeekGraph()2722 DrawSeekGraph ()
2723 {
2724     int i;
2725     if(!seekGraphUp) return FALSE;
2726     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2727     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2728 
2729     DrawSeekBackground(0, 0, w, h);
2730     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2731     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2732     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2733 	int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2734 	yy = h-1-yy;
2735 	DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2736 	if(i%500 == 0) {
2737 	    char buf[MSG_SIZ];
2738 	    snprintf(buf, MSG_SIZ, "%d", i);
2739 	    DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2740 	}
2741     }
2742     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2743     for(i=1; i<100; i+=(i<10?1:5)) {
2744 	int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2745 	DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2746 	if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2747 	    char buf[MSG_SIZ];
2748 	    snprintf(buf, MSG_SIZ, "%d", i);
2749 	    DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2750 	}
2751     }
2752     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2753     return TRUE;
2754 }
2755 
2756 int
SeekGraphClick(ClickType click,int x,int y,int moving)2757 SeekGraphClick (ClickType click, int x, int y, int moving)
2758 {
2759     static int lastDown = 0, displayed = 0, lastSecond;
2760     if(y < 0) return FALSE;
2761     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2762 	(gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2763 	if(!seekGraphUp) return FALSE;
2764 	seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2765 	DrawPosition(TRUE, NULL);
2766 	return TRUE;
2767     }
2768     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2769 	if(click == Release || moving) return FALSE;
2770 	nrOfSeekAds = 0;
2771 	soughtPending = TRUE;
2772 	SendToICS(ics_prefix);
2773 	SendToICS("sought\n"); // should this be "sought all"?
2774     } else { // issue challenge based on clicked ad
2775 	int dist = 10000; int i, closest = 0, second = 0;
2776 	for(i=0; i<nrOfSeekAds; i++) {
2777 	    int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2778 	    if(d < dist) { dist = d; closest = i; }
2779 	    second += (d - zList[i] < 120); // count in-range ads
2780 	    if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2781 	}
2782 	if(dist < 120) {
2783 	    char buf[MSG_SIZ];
2784 	    second = (second > 1);
2785 	    if(displayed != closest || second != lastSecond) {
2786 		DisplayMessage(second ? "!" : "", seekAdList[closest]);
2787 		lastSecond = second; displayed = closest;
2788 	    }
2789 	    if(click == Press) {
2790 		if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2791 		lastDown = closest;
2792 		return TRUE;
2793 	    } // on press 'hit', only show info
2794 	    if(moving == 2) return TRUE; // ignore right up-clicks on dot
2795 	    snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2796 	    SendToICS(ics_prefix);
2797 	    SendToICS(buf);
2798 	    return TRUE; // let incoming board of started game pop down the graph
2799 	} else if(click == Release) { // release 'miss' is ignored
2800 	    zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2801 	    if(moving == 2) { // right up-click
2802 		nrOfSeekAds = 0; // refresh graph
2803 		soughtPending = TRUE;
2804 		SendToICS(ics_prefix);
2805 		SendToICS("sought\n"); // should this be "sought all"?
2806 	    }
2807 	    return TRUE;
2808 	} else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2809 	// press miss or release hit 'pop down' seek graph
2810 	seekGraphUp = FALSE;
2811 	DrawPosition(TRUE, NULL);
2812     }
2813     return TRUE;
2814 }
2815 
2816 void
read_from_ics(InputSourceRef isr,VOIDSTAR closure,char * data,int count,int error)2817 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2818 {
2819 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2820 #define STARTED_NONE 0
2821 #define STARTED_MOVES 1
2822 #define STARTED_BOARD 2
2823 #define STARTED_OBSERVE 3
2824 #define STARTED_HOLDINGS 4
2825 #define STARTED_CHATTER 5
2826 #define STARTED_COMMENT 6
2827 #define STARTED_MOVES_NOHIDE 7
2828 
2829     static int started = STARTED_NONE;
2830     static char parse[20000];
2831     static int parse_pos = 0;
2832     static char buf[BUF_SIZE + 1];
2833     static int firstTime = TRUE, intfSet = FALSE;
2834     static ColorClass prevColor = ColorNormal;
2835     static int savingComment = FALSE;
2836     static int cmatch = 0; // continuation sequence match
2837     char *bp;
2838     char str[MSG_SIZ];
2839     int i, oldi;
2840     int buf_len;
2841     int next_out;
2842     int tkind;
2843     int backup;    /* [DM] For zippy color lines */
2844     char *p;
2845     char talker[MSG_SIZ]; // [HGM] chat
2846     int channel, collective=0;
2847 
2848     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2849 
2850     if (appData.debugMode) {
2851       if (!error) {
2852 	fprintf(debugFP, "<ICS: ");
2853 	show_bytes(debugFP, data, count);
2854 	fprintf(debugFP, "\n");
2855       }
2856     }
2857 
2858     if (appData.debugMode) { int f = forwardMostMove;
2859         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2860                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2861                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2862     }
2863     if (count > 0) {
2864 	/* If last read ended with a partial line that we couldn't parse,
2865 	   prepend it to the new read and try again. */
2866 	if (leftover_len > 0) {
2867 	    for (i=0; i<leftover_len; i++)
2868 	      buf[i] = buf[leftover_start + i];
2869 	}
2870 
2871     /* copy new characters into the buffer */
2872     bp = buf + leftover_len;
2873     buf_len=leftover_len;
2874     for (i=0; i<count; i++)
2875     {
2876         // ignore these
2877         if (data[i] == '\r')
2878             continue;
2879 
2880         // join lines split by ICS?
2881         if (!appData.noJoin)
2882         {
2883             /*
2884                 Joining just consists of finding matches against the
2885                 continuation sequence, and discarding that sequence
2886                 if found instead of copying it.  So, until a match
2887                 fails, there's nothing to do since it might be the
2888                 complete sequence, and thus, something we don't want
2889                 copied.
2890             */
2891             if (data[i] == cont_seq[cmatch])
2892             {
2893                 cmatch++;
2894                 if (cmatch == strlen(cont_seq))
2895                 {
2896                     cmatch = 0; // complete match.  just reset the counter
2897 
2898                     /*
2899                         it's possible for the ICS to not include the space
2900                         at the end of the last word, making our [correct]
2901                         join operation fuse two separate words.  the server
2902                         does this when the space occurs at the width setting.
2903                     */
2904                     if (!buf_len || buf[buf_len-1] != ' ')
2905                     {
2906                         *bp++ = ' ';
2907                         buf_len++;
2908                     }
2909                 }
2910                 continue;
2911             }
2912             else if (cmatch)
2913             {
2914                 /*
2915                     match failed, so we have to copy what matched before
2916                     falling through and copying this character.  In reality,
2917                     this will only ever be just the newline character, but
2918                     it doesn't hurt to be precise.
2919                 */
2920                 strncpy(bp, cont_seq, cmatch);
2921                 bp += cmatch;
2922                 buf_len += cmatch;
2923                 cmatch = 0;
2924             }
2925         }
2926 
2927         // copy this char
2928         *bp++ = data[i];
2929         buf_len++;
2930     }
2931 
2932 	buf[buf_len] = NULLCHAR;
2933 //	next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2934 	next_out = 0;
2935 	leftover_start = 0;
2936 
2937 	i = 0;
2938 	while (i < buf_len) {
2939 	    /* Deal with part of the TELNET option negotiation
2940 	       protocol.  We refuse to do anything beyond the
2941 	       defaults, except that we allow the WILL ECHO option,
2942 	       which ICS uses to turn off password echoing when we are
2943 	       directly connected to it.  We reject this option
2944 	       if localLineEditing mode is on (always on in xboard)
2945                and we are talking to port 23, which might be a real
2946 	       telnet server that will try to keep WILL ECHO on permanently.
2947              */
2948 	    if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2949 		static int remoteEchoOption = FALSE; /* telnet ECHO option */
2950 		unsigned char option;
2951 		oldi = i;
2952 		switch ((unsigned char) buf[++i]) {
2953 		  case TN_WILL:
2954 		    if (appData.debugMode)
2955 		      fprintf(debugFP, "\n<WILL ");
2956 		    switch (option = (unsigned char) buf[++i]) {
2957 		      case TN_ECHO:
2958 			if (appData.debugMode)
2959 			  fprintf(debugFP, "ECHO ");
2960 			/* Reply only if this is a change, according
2961 			   to the protocol rules. */
2962 			if (remoteEchoOption) break;
2963 			if (appData.localLineEditing &&
2964 			    atoi(appData.icsPort) == TN_PORT) {
2965 			    TelnetRequest(TN_DONT, TN_ECHO);
2966 			} else {
2967 			    EchoOff();
2968 			    TelnetRequest(TN_DO, TN_ECHO);
2969 			    remoteEchoOption = TRUE;
2970 			}
2971 			break;
2972 		      default:
2973 			if (appData.debugMode)
2974 			  fprintf(debugFP, "%d ", option);
2975 			/* Whatever this is, we don't want it. */
2976 			TelnetRequest(TN_DONT, option);
2977 			break;
2978 		    }
2979 		    break;
2980 		  case TN_WONT:
2981 		    if (appData.debugMode)
2982 		      fprintf(debugFP, "\n<WONT ");
2983 		    switch (option = (unsigned char) buf[++i]) {
2984 		      case TN_ECHO:
2985 			if (appData.debugMode)
2986 			  fprintf(debugFP, "ECHO ");
2987 			/* Reply only if this is a change, according
2988 			   to the protocol rules. */
2989 			if (!remoteEchoOption) break;
2990 			EchoOn();
2991 			TelnetRequest(TN_DONT, TN_ECHO);
2992 			remoteEchoOption = FALSE;
2993 			break;
2994 		      default:
2995 			if (appData.debugMode)
2996 			  fprintf(debugFP, "%d ", (unsigned char) option);
2997 			/* Whatever this is, it must already be turned
2998 			   off, because we never agree to turn on
2999 			   anything non-default, so according to the
3000 			   protocol rules, we don't reply. */
3001 			break;
3002 		    }
3003 		    break;
3004 		  case TN_DO:
3005 		    if (appData.debugMode)
3006 		      fprintf(debugFP, "\n<DO ");
3007 		    switch (option = (unsigned char) buf[++i]) {
3008 		      default:
3009 			/* Whatever this is, we refuse to do it. */
3010 			if (appData.debugMode)
3011 			  fprintf(debugFP, "%d ", option);
3012 			TelnetRequest(TN_WONT, option);
3013 			break;
3014 		    }
3015 		    break;
3016 		  case TN_DONT:
3017 		    if (appData.debugMode)
3018 		      fprintf(debugFP, "\n<DONT ");
3019 		    switch (option = (unsigned char) buf[++i]) {
3020 		      default:
3021 			if (appData.debugMode)
3022 			  fprintf(debugFP, "%d ", option);
3023 			/* Whatever this is, we are already not doing
3024 			   it, because we never agree to do anything
3025 			   non-default, so according to the protocol
3026 			   rules, we don't reply. */
3027 			break;
3028 		    }
3029 		    break;
3030 		  case TN_IAC:
3031 		    if (appData.debugMode)
3032 		      fprintf(debugFP, "\n<IAC ");
3033 		    /* Doubled IAC; pass it through */
3034 		    i--;
3035 		    break;
3036 		  default:
3037 		    if (appData.debugMode)
3038 		      fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3039 		    /* Drop all other telnet commands on the floor */
3040 		    break;
3041 		}
3042 		if (oldi > next_out)
3043 		  SendToPlayer(&buf[next_out], oldi - next_out);
3044 		if (++i > next_out)
3045 		  next_out = i;
3046 		continue;
3047 	    }
3048 
3049 	    /* OK, this at least will *usually* work */
3050 	    if (!loggedOn && looking_at(buf, &i, "ics%")) {
3051 		loggedOn = TRUE;
3052 	    }
3053 
3054 	    if (loggedOn && !intfSet) {
3055 		if (ics_type == ICS_ICC) {
3056 		  snprintf(str, MSG_SIZ,
3057 			  "/set-quietly interface %s\n/set-quietly style 12\n",
3058 			  programVersion);
3059 		  if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3060 		      strcat(str, "/set-2 51 1\n/set seek 1\n");
3061 		} else if (ics_type == ICS_CHESSNET) {
3062 		  snprintf(str, MSG_SIZ, "/style 12\n");
3063 		} else {
3064 		  safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3065 		  strcat(str, programVersion);
3066 		  strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3067 		  if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068 		      strcat(str, "$iset seekremove 1\n$set seek 1\n");
3069 #ifdef WIN32
3070 		  strcat(str, "$iset nohighlight 1\n");
3071 #endif
3072 		  strcat(str, "$iset lock 1\n$style 12\n");
3073 		}
3074 		SendToICS(str);
3075 		NotifyFrontendLogin();
3076 		intfSet = TRUE;
3077 	    }
3078 
3079 	    if (started == STARTED_COMMENT) {
3080 		/* Accumulate characters in comment */
3081 		parse[parse_pos++] = buf[i];
3082 		if (buf[i] == '\n') {
3083 		    parse[parse_pos] = NULLCHAR;
3084 		    if(chattingPartner>=0) {
3085 			char mess[MSG_SIZ];
3086 			snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3087 			OutputChatMessage(chattingPartner, mess);
3088 			if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3089 			    int p;
3090 			    talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3091 			    for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3092 				snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3093 				OutputChatMessage(p, mess);
3094 				break;
3095 			    }
3096 			}
3097 			chattingPartner = -1;
3098 			if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3099 			collective = 0;
3100 		    } else
3101 		    if(!suppressKibitz) // [HGM] kibitz
3102 			AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3103 		    else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3104 			int nrDigit = 0, nrAlph = 0, j;
3105 			if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3106 			{ parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3107 			parse[parse_pos] = NULLCHAR;
3108 			// try to be smart: if it does not look like search info, it should go to
3109 			// ICS interaction window after all, not to engine-output window.
3110 			for(j=0; j<parse_pos; j++) { // count letters and digits
3111 			    nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3112 			    nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3113 			    nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3114 			}
3115 			if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3116 			    int depth=0; float score;
3117 			    if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3118 				// [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3119 				pvInfoList[forwardMostMove-1].depth = depth;
3120 				pvInfoList[forwardMostMove-1].score = 100*score;
3121 			    }
3122 			    OutputKibitz(suppressKibitz, parse);
3123 			} else {
3124 			    char tmp[MSG_SIZ];
3125 			    if(gameMode == IcsObserving) // restore original ICS messages
3126 			      /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3127 			      snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3128 			    else
3129 			    /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3130 			    snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3131 			    SendToPlayer(tmp, strlen(tmp));
3132 			}
3133 			next_out = i+1; // [HGM] suppress printing in ICS window
3134 		    }
3135 		    started = STARTED_NONE;
3136 		} else {
3137 		    /* Don't match patterns against characters in comment */
3138 		    i++;
3139 		    continue;
3140 		}
3141 	    }
3142 	    if (started == STARTED_CHATTER) {
3143 		if (buf[i] != '\n') {
3144 		    /* Don't match patterns against characters in chatter */
3145 		    i++;
3146 		    continue;
3147 		}
3148 		started = STARTED_NONE;
3149 		if(suppressKibitz) next_out = i+1;
3150 	    }
3151 
3152             /* Kludge to deal with rcmd protocol */
3153 	    if (firstTime && looking_at(buf, &i, "\001*")) {
3154 		DisplayFatalError(&buf[1], 0, 1);
3155 		continue;
3156 	    } else {
3157 	        firstTime = FALSE;
3158 	    }
3159 
3160 	    if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3161 		ics_type = ICS_ICC;
3162 		ics_prefix = "/";
3163 		if (appData.debugMode)
3164 		  fprintf(debugFP, "ics_type %d\n", ics_type);
3165 		continue;
3166 	    }
3167 	    if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3168 		ics_type = ICS_FICS;
3169 		ics_prefix = "$";
3170 		if (appData.debugMode)
3171 		  fprintf(debugFP, "ics_type %d\n", ics_type);
3172 		continue;
3173 	    }
3174 	    if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3175 		ics_type = ICS_CHESSNET;
3176 		ics_prefix = "/";
3177 		if (appData.debugMode)
3178 		  fprintf(debugFP, "ics_type %d\n", ics_type);
3179 		continue;
3180 	    }
3181 
3182 	    if (!loggedOn &&
3183 		(looking_at(buf, &i, "\"*\" is *a registered name") ||
3184 		 looking_at(buf, &i, "Logging you in as \"*\"") ||
3185 		 looking_at(buf, &i, "will be \"*\""))) {
3186 	      safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3187 	      continue;
3188 	    }
3189 
3190 	    if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3191 	      char buf[MSG_SIZ];
3192 	      snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3193 	      DisplayIcsInteractionTitle(buf);
3194 	      have_set_title = TRUE;
3195 	    }
3196 
3197 	    /* skip finger notes */
3198 	    if (started == STARTED_NONE &&
3199 		((buf[i] == ' ' && isdigit(buf[i+1])) ||
3200 		 (buf[i] == '1' && buf[i+1] == '0')) &&
3201 		buf[i+2] == ':' && buf[i+3] == ' ') {
3202 	      started = STARTED_CHATTER;
3203 	      i += 3;
3204 	      continue;
3205 	    }
3206 
3207 	    oldi = i;
3208 	    // [HGM] seekgraph: recognize sought lines and end-of-sought message
3209 	    if(appData.seekGraph) {
3210 		if(soughtPending && MatchSoughtLine(buf+i)) {
3211 		    i = strstr(buf+i, "rated") - buf;
3212 		    if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3213 		    next_out = leftover_start = i;
3214 		    started = STARTED_CHATTER;
3215 		    suppressKibitz = TRUE;
3216 		    continue;
3217 		}
3218 		if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3219 			&& looking_at(buf, &i, "* ads displayed")) {
3220 		    soughtPending = FALSE;
3221 		    seekGraphUp = TRUE;
3222 		    DrawSeekGraph();
3223 		    continue;
3224 		}
3225 		if(appData.autoRefresh) {
3226 		    if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3227 			int s = (ics_type == ICS_ICC); // ICC format differs
3228 			if(seekGraphUp)
3229 			AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3230 			      star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3231 			looking_at(buf, &i, "*% "); // eat prompt
3232 			if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3233 			if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3234 			next_out = i; // suppress
3235 			continue;
3236 		    }
3237 		    if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3238 			char *p = star_match[0];
3239 			while(*p) {
3240 			    if(seekGraphUp) RemoveSeekAd(atoi(p));
3241 			    while(*p && *p++ != ' '); // next
3242 			}
3243 			looking_at(buf, &i, "*% "); // eat prompt
3244 			if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3245 			next_out = i;
3246 			continue;
3247 		    }
3248 		}
3249 	    }
3250 
3251 	    /* skip formula vars */
3252 	    if (started == STARTED_NONE &&
3253 		buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3254 	      started = STARTED_CHATTER;
3255 	      i += 3;
3256 	      continue;
3257 	    }
3258 
3259 	    // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3260 	    if (appData.autoKibitz && started == STARTED_NONE &&
3261                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3262 		(gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3263 		if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3264 		    looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3265 		   (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3266 		    StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3267 		    	suppressKibitz = TRUE;
3268 			if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3269 			next_out = i;
3270 			if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3271 				&& (gameMode == IcsPlayingWhite)) ||
3272 			   (StrStr(star_match[0], gameInfo.black) == star_match[0]
3273 				&& (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3274 			    started = STARTED_CHATTER; // own kibitz we simply discard
3275 			else {
3276 			    started = STARTED_COMMENT; // make sure it will be collected in parse[]
3277 			    parse_pos = 0; parse[0] = NULLCHAR;
3278 			    savingComment = TRUE;
3279 			    suppressKibitz = gameMode != IcsObserving ? 2 :
3280 				(StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3281 			}
3282 			continue;
3283 		} else
3284 		if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3285 		    looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3286 			 && atoi(star_match[0])) {
3287 		    // suppress the acknowledgements of our own autoKibitz
3288 		    char *p;
3289 		    if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3290 		    if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3291 		    SendToPlayer(star_match[0], strlen(star_match[0]));
3292 		    if(looking_at(buf, &i, "*% ")) // eat prompt
3293 			suppressKibitz = FALSE;
3294 		    next_out = i;
3295 		    continue;
3296 		}
3297 	    } // [HGM] kibitz: end of patch
3298 
3299 	    if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3300 
3301 	    // [HGM] chat: intercept tells by users for which we have an open chat window
3302 	    channel = -1;
3303 	    if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3304 					   looking_at(buf, &i, "* whispers:") ||
3305 					   looking_at(buf, &i, "* kibitzes:") ||
3306 					   looking_at(buf, &i, "* shouts:") ||
3307 					   looking_at(buf, &i, "* c-shouts:") ||
3308 					   looking_at(buf, &i, "--> * ") ||
3309 					   looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3310 					   looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3311 					   looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3312 					   looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3313 		int p;
3314 		sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3315 		chattingPartner = -1; collective = 0;
3316 
3317 		if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3318 		for(p=0; p<MAX_CHAT; p++) {
3319 		    collective = 1;
3320 		    if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3321 		    talker[0] = '['; strcat(talker, "] ");
3322 		    Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3323 		    chattingPartner = p; break;
3324 		    }
3325 		} else
3326 		if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3327 		for(p=0; p<MAX_CHAT; p++) {
3328 		    collective = 1;
3329 		    if(!strcmp("kibitzes", chatPartner[p])) {
3330 			talker[0] = '['; strcat(talker, "] ");
3331 			chattingPartner = p; break;
3332 		    }
3333 		} else
3334 		if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3335 		for(p=0; p<MAX_CHAT; p++) {
3336 		    collective = 1;
3337 		    if(!strcmp("whispers", chatPartner[p])) {
3338 			talker[0] = '['; strcat(talker, "] ");
3339 			chattingPartner = p; break;
3340 		    }
3341 		} else
3342 		if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3343 		  if(buf[i-8] == '-' && buf[i-3] == 't')
3344 		  for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3345 		    collective = 1;
3346 		    if(!strcmp("c-shouts", chatPartner[p])) {
3347 			talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3348 			chattingPartner = p; break;
3349 		    }
3350 		  }
3351 		  if(chattingPartner < 0)
3352 		  for(p=0; p<MAX_CHAT; p++) {
3353 		    collective = 1;
3354 		    if(!strcmp("shouts", chatPartner[p])) {
3355 			if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3356 			else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3357 			else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3358 			chattingPartner = p; break;
3359 		    }
3360 		  }
3361 		}
3362 		if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3363 		for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3364 		    talker[0] = 0;
3365 		    Colorize(ColorTell, FALSE);
3366 		    if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3367 		    collective |= 2;
3368 		    chattingPartner = p; break;
3369 		}
3370 		if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3371 		    Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3372 		    started = STARTED_COMMENT;
3373 		    parse_pos = 0; parse[0] = NULLCHAR;
3374 		    savingComment = 3 + chattingPartner; // counts as TRUE
3375 		    if(collective == 3) i = oldi; else {
3376 			suppressKibitz = TRUE;
3377 			if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3378 			if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3379 			continue;
3380 		    }
3381 		}
3382 	    } // [HGM] chat: end of patch
3383 
3384           backup = i;
3385 	    if (appData.zippyTalk || appData.zippyPlay) {
3386                 /* [DM] Backup address for color zippy lines */
3387 #if ZIPPY
3388                if (loggedOn == TRUE)
3389                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3390                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3391 #endif
3392 	    } // [DM] 'else { ' deleted
3393 	 	if (
3394 		    /* Regular tells and says */
3395 		    (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3396 		    looking_at(buf, &i, "* (your partner) tells you: ") ||
3397 		    looking_at(buf, &i, "* says: ") ||
3398 		    /* Don't color "message" or "messages" output */
3399 		    (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3400 		    looking_at(buf, &i, "*. * at *:*: ") ||
3401 		    looking_at(buf, &i, "--* (*:*): ") ||
3402 		    /* Message notifications (same color as tells) */
3403 		    looking_at(buf, &i, "* has left a message ") ||
3404 		    looking_at(buf, &i, "* just sent you a message:\n") ||
3405 		    /* Whispers and kibitzes */
3406 		    (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3407  		    looking_at(buf, &i, "* kibitzes: ") ||
3408  		    /* Channel tells */
3409  		    (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3410 
3411 		  if (tkind == 1 && strchr(star_match[0], ':')) {
3412 		      /* Avoid "tells you:" spoofs in channels */
3413 		     tkind = 3;
3414 		  }
3415 		  if (star_match[0][0] == NULLCHAR ||
3416 		      strchr(star_match[0], ' ') ||
3417 		      (tkind == 3 && strchr(star_match[1], ' '))) {
3418 		    /* Reject bogus matches */
3419 		    i = oldi;
3420 		  } else {
3421 		    if (appData.colorize) {
3422 		      if (oldi > next_out) {
3423 			SendToPlayer(&buf[next_out], oldi - next_out);
3424 			next_out = oldi;
3425 		      }
3426 		      switch (tkind) {
3427 		      case 1:
3428 			Colorize(ColorTell, FALSE);
3429 			curColor = ColorTell;
3430 			break;
3431 		      case 2:
3432 			Colorize(ColorKibitz, FALSE);
3433 			curColor = ColorKibitz;
3434 			break;
3435 		      case 3:
3436 			p = strrchr(star_match[1], '(');
3437 			if (p == NULL) {
3438 			  p = star_match[1];
3439 			} else {
3440 			  p++;
3441 			}
3442 			if (atoi(p) == 1) {
3443 			  Colorize(ColorChannel1, FALSE);
3444 			  curColor = ColorChannel1;
3445 			} else {
3446 			  Colorize(ColorChannel, FALSE);
3447 			  curColor = ColorChannel;
3448 			}
3449 			break;
3450 		      case 5:
3451 			curColor = ColorNormal;
3452 			break;
3453 		      }
3454 		    }
3455 		    if (started == STARTED_NONE && appData.autoComment &&
3456 			(gameMode == IcsObserving ||
3457 			 gameMode == IcsPlayingWhite ||
3458 			 gameMode == IcsPlayingBlack)) {
3459 		      parse_pos = i - oldi;
3460 		      memcpy(parse, &buf[oldi], parse_pos);
3461 		      parse[parse_pos] = NULLCHAR;
3462 		      started = STARTED_COMMENT;
3463 		      savingComment = TRUE;
3464 		    } else if(collective != 3) {
3465 		      started = STARTED_CHATTER;
3466 		      savingComment = FALSE;
3467 		    }
3468 		    loggedOn = TRUE;
3469 		    continue;
3470 		  }
3471 		}
3472 
3473 		if (looking_at(buf, &i, "* s-shouts: ") ||
3474 		    looking_at(buf, &i, "* c-shouts: ")) {
3475 		    if (appData.colorize) {
3476 			if (oldi > next_out) {
3477 			    SendToPlayer(&buf[next_out], oldi - next_out);
3478 			    next_out = oldi;
3479 			}
3480 			Colorize(ColorSShout, FALSE);
3481 			curColor = ColorSShout;
3482 		    }
3483 		    loggedOn = TRUE;
3484 		    started = STARTED_CHATTER;
3485 		    continue;
3486 		}
3487 
3488 		if (looking_at(buf, &i, "--->")) {
3489 		    loggedOn = TRUE;
3490 		    continue;
3491 		}
3492 
3493 		if (looking_at(buf, &i, "* shouts: ") ||
3494 		    looking_at(buf, &i, "--> ")) {
3495 		    if (appData.colorize) {
3496 			if (oldi > next_out) {
3497 			    SendToPlayer(&buf[next_out], oldi - next_out);
3498 			    next_out = oldi;
3499 			}
3500 			Colorize(ColorShout, FALSE);
3501 			curColor = ColorShout;
3502 		    }
3503 		    loggedOn = TRUE;
3504 		    started = STARTED_CHATTER;
3505 		    continue;
3506 		}
3507 
3508 		if (looking_at( buf, &i, "Challenge:")) {
3509 		    if (appData.colorize) {
3510 			if (oldi > next_out) {
3511 			    SendToPlayer(&buf[next_out], oldi - next_out);
3512 			    next_out = oldi;
3513 			}
3514 			Colorize(ColorChallenge, FALSE);
3515 			curColor = ColorChallenge;
3516 		    }
3517 		    loggedOn = TRUE;
3518 		    continue;
3519 		}
3520 
3521 		if (looking_at(buf, &i, "* offers you") ||
3522 		    looking_at(buf, &i, "* offers to be") ||
3523 		    looking_at(buf, &i, "* would like to") ||
3524 		    looking_at(buf, &i, "* requests to") ||
3525 		    looking_at(buf, &i, "Your opponent offers") ||
3526 		    looking_at(buf, &i, "Your opponent requests")) {
3527 
3528 		    if (appData.colorize) {
3529 			if (oldi > next_out) {
3530 			    SendToPlayer(&buf[next_out], oldi - next_out);
3531 			    next_out = oldi;
3532 			}
3533 			Colorize(ColorRequest, FALSE);
3534 			curColor = ColorRequest;
3535 		    }
3536 		    continue;
3537 		}
3538 
3539 		if (looking_at(buf, &i, "* (*) seeking")) {
3540 		    if (appData.colorize) {
3541 			if (oldi > next_out) {
3542 			    SendToPlayer(&buf[next_out], oldi - next_out);
3543 			    next_out = oldi;
3544 			}
3545 			Colorize(ColorSeek, FALSE);
3546 			curColor = ColorSeek;
3547 		    }
3548 		    continue;
3549 	    }
3550 
3551           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3552 
3553 	    if (looking_at(buf, &i, "\\   ")) {
3554 		if (prevColor != ColorNormal) {
3555 		    if (oldi > next_out) {
3556 			SendToPlayer(&buf[next_out], oldi - next_out);
3557 			next_out = oldi;
3558 		    }
3559 		    Colorize(prevColor, TRUE);
3560 		    curColor = prevColor;
3561 		}
3562 		if (savingComment) {
3563 		    parse_pos = i - oldi;
3564 		    memcpy(parse, &buf[oldi], parse_pos);
3565 		    parse[parse_pos] = NULLCHAR;
3566 		    started = STARTED_COMMENT;
3567 		    if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3568 		        chattingPartner = savingComment - 3; // kludge to remember the box
3569 		} else {
3570 		    started = STARTED_CHATTER;
3571 		}
3572 		continue;
3573 	    }
3574 
3575 	    if (looking_at(buf, &i, "Black Strength :") ||
3576 		looking_at(buf, &i, "<<< style 10 board >>>") ||
3577 		looking_at(buf, &i, "<10>") ||
3578 		looking_at(buf, &i, "#@#")) {
3579 		/* Wrong board style */
3580 		loggedOn = TRUE;
3581 		SendToICS(ics_prefix);
3582 		SendToICS("set style 12\n");
3583 		SendToICS(ics_prefix);
3584     	        SendToICS("refresh\n");
3585 		continue;
3586 	    }
3587 
3588 	    if (looking_at(buf, &i, "login:")) {
3589 	      if (!have_sent_ICS_logon) {
3590 		if(ICSInitScript())
3591 		  have_sent_ICS_logon = 1;
3592 		else // no init script was found
3593 		  have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3594 	      } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3595 		  have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3596 	      }
3597 		continue;
3598 	    }
3599 
3600 	    if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3601 		(looking_at(buf, &i, "\n<12> ") ||
3602 		 looking_at(buf, &i, "<12> "))) {
3603 		loggedOn = TRUE;
3604 		if (oldi > next_out) {
3605 		    SendToPlayer(&buf[next_out], oldi - next_out);
3606 		}
3607 		next_out = i;
3608 		started = STARTED_BOARD;
3609 		parse_pos = 0;
3610 		continue;
3611 	    }
3612 
3613 	    if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3614 		looking_at(buf, &i, "<b1> ")) {
3615 		if (oldi > next_out) {
3616 		    SendToPlayer(&buf[next_out], oldi - next_out);
3617 		}
3618 		next_out = i;
3619 		started = STARTED_HOLDINGS;
3620 		parse_pos = 0;
3621 		continue;
3622 	    }
3623 
3624 	    if (looking_at(buf, &i, "* *vs. * *--- *")) {
3625 		loggedOn = TRUE;
3626 		/* Header for a move list -- first line */
3627 
3628 		switch (ics_getting_history) {
3629 		  case H_FALSE:
3630 		    switch (gameMode) {
3631 		      case IcsIdle:
3632 		      case BeginningOfGame:
3633 			/* User typed "moves" or "oldmoves" while we
3634 			   were idle.  Pretend we asked for these
3635 			   moves and soak them up so user can step
3636 			   through them and/or save them.
3637 			   */
3638 			Reset(FALSE, TRUE);
3639 			gameMode = IcsObserving;
3640 			ModeHighlight();
3641 			ics_gamenum = -1;
3642 			ics_getting_history = H_GOT_UNREQ_HEADER;
3643 			break;
3644 		      case EditGame: /*?*/
3645 		      case EditPosition: /*?*/
3646 			/* Should above feature work in these modes too? */
3647 			/* For now it doesn't */
3648 			ics_getting_history = H_GOT_UNWANTED_HEADER;
3649 			break;
3650 		      default:
3651 			ics_getting_history = H_GOT_UNWANTED_HEADER;
3652 			break;
3653 		    }
3654 		    break;
3655 		  case H_REQUESTED:
3656 		    /* Is this the right one? */
3657 		    if (gameInfo.white && gameInfo.black &&
3658 			strcmp(gameInfo.white, star_match[0]) == 0 &&
3659 			strcmp(gameInfo.black, star_match[2]) == 0) {
3660 			/* All is well */
3661 			ics_getting_history = H_GOT_REQ_HEADER;
3662 		    }
3663 		    break;
3664 		  case H_GOT_REQ_HEADER:
3665 		  case H_GOT_UNREQ_HEADER:
3666 		  case H_GOT_UNWANTED_HEADER:
3667 		  case H_GETTING_MOVES:
3668 		    /* Should not happen */
3669 		    DisplayError(_("Error gathering move list: two headers"), 0);
3670 		    ics_getting_history = H_FALSE;
3671 		    break;
3672 		}
3673 
3674 		/* Save player ratings into gameInfo if needed */
3675 		if ((ics_getting_history == H_GOT_REQ_HEADER ||
3676 		     ics_getting_history == H_GOT_UNREQ_HEADER) &&
3677 		    (gameInfo.whiteRating == -1 ||
3678 		     gameInfo.blackRating == -1)) {
3679 
3680 		    gameInfo.whiteRating = string_to_rating(star_match[1]);
3681 		    gameInfo.blackRating = string_to_rating(star_match[3]);
3682 		    if (appData.debugMode)
3683 		      fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3684 			      gameInfo.whiteRating, gameInfo.blackRating);
3685 		}
3686 		continue;
3687 	    }
3688 
3689 	    if (looking_at(buf, &i,
3690 	      "* * match, initial time: * minute*, increment: * second")) {
3691 		/* Header for a move list -- second line */
3692 		/* Initial board will follow if this is a wild game */
3693 		if (gameInfo.event != NULL) free(gameInfo.event);
3694 		snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3695 		gameInfo.event = StrSave(str);
3696                 /* [HGM] we switched variant. Translate boards if needed. */
3697                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3698 		continue;
3699 	    }
3700 
3701 	    if (looking_at(buf, &i, "Move  ")) {
3702 		/* Beginning of a move list */
3703 		switch (ics_getting_history) {
3704 		  case H_FALSE:
3705 		    /* Normally should not happen */
3706 		    /* Maybe user hit reset while we were parsing */
3707 		    break;
3708 		  case H_REQUESTED:
3709 		    /* Happens if we are ignoring a move list that is not
3710 		     * the one we just requested.  Common if the user
3711 		     * tries to observe two games without turning off
3712 		     * getMoveList */
3713 		    break;
3714 		  case H_GETTING_MOVES:
3715 		    /* Should not happen */
3716 		    DisplayError(_("Error gathering move list: nested"), 0);
3717 		    ics_getting_history = H_FALSE;
3718 		    break;
3719 		  case H_GOT_REQ_HEADER:
3720 		    ics_getting_history = H_GETTING_MOVES;
3721 		    started = STARTED_MOVES;
3722 		    parse_pos = 0;
3723 		    if (oldi > next_out) {
3724 			SendToPlayer(&buf[next_out], oldi - next_out);
3725 		    }
3726 		    break;
3727 		  case H_GOT_UNREQ_HEADER:
3728 		    ics_getting_history = H_GETTING_MOVES;
3729 		    started = STARTED_MOVES_NOHIDE;
3730 		    parse_pos = 0;
3731 		    break;
3732 		  case H_GOT_UNWANTED_HEADER:
3733 		    ics_getting_history = H_FALSE;
3734 		    break;
3735 		}
3736 		continue;
3737 	    }
3738 
3739 	    if (looking_at(buf, &i, "% ") ||
3740 		((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3741 		 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3742 		if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3743 		    soughtPending = FALSE;
3744 		    seekGraphUp = TRUE;
3745 		    DrawSeekGraph();
3746 		}
3747 		if(suppressKibitz) next_out = i;
3748 		savingComment = FALSE;
3749 		suppressKibitz = 0;
3750 		switch (started) {
3751 		  case STARTED_MOVES:
3752 		  case STARTED_MOVES_NOHIDE:
3753 		    memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3754 		    parse[parse_pos + i - oldi] = NULLCHAR;
3755 		    ParseGameHistory(parse);
3756 #if ZIPPY
3757 		    if (appData.zippyPlay && first.initDone) {
3758 		        FeedMovesToProgram(&first, forwardMostMove);
3759 			if (gameMode == IcsPlayingWhite) {
3760 			    if (WhiteOnMove(forwardMostMove)) {
3761 				if (first.sendTime) {
3762 				  if (first.useColors) {
3763 				    SendToProgram("black\n", &first);
3764 				  }
3765 				  SendTimeRemaining(&first, TRUE);
3766 				}
3767 				if (first.useColors) {
3768 				  SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3769 				}
3770 				bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3771 				first.maybeThinking = TRUE;
3772 			    } else {
3773 			        if (first.usePlayother) {
3774 				  if (first.sendTime) {
3775 				    SendTimeRemaining(&first, TRUE);
3776 				  }
3777 				  SendToProgram("playother\n", &first);
3778 				  firstMove = FALSE;
3779 			        } else {
3780 				  firstMove = TRUE;
3781 				}
3782 			    }
3783 			} else if (gameMode == IcsPlayingBlack) {
3784 			    if (!WhiteOnMove(forwardMostMove)) {
3785 				if (first.sendTime) {
3786 				  if (first.useColors) {
3787 				    SendToProgram("white\n", &first);
3788 				  }
3789 				  SendTimeRemaining(&first, FALSE);
3790 				}
3791 				if (first.useColors) {
3792 				  SendToProgram("black\n", &first);
3793 				}
3794 				bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3795 				first.maybeThinking = TRUE;
3796 			    } else {
3797 			        if (first.usePlayother) {
3798 				  if (first.sendTime) {
3799 				    SendTimeRemaining(&first, FALSE);
3800 				  }
3801 				  SendToProgram("playother\n", &first);
3802 				  firstMove = FALSE;
3803 			        } else {
3804 				  firstMove = TRUE;
3805 				}
3806 			    }
3807 			}
3808 		    }
3809 #endif
3810 		    if (gameMode == IcsObserving && ics_gamenum == -1) {
3811 			/* Moves came from oldmoves or moves command
3812 			   while we weren't doing anything else.
3813 			   */
3814 			currentMove = forwardMostMove;
3815 			ClearHighlights();/*!!could figure this out*/
3816 			flipView = appData.flipView;
3817 			DrawPosition(TRUE, boards[currentMove]);
3818 			DisplayBothClocks();
3819 			snprintf(str, MSG_SIZ, "%s %s %s",
3820 				gameInfo.white, _("vs."),  gameInfo.black);
3821 			DisplayTitle(str);
3822 			gameMode = IcsIdle;
3823 		    } else {
3824 			/* Moves were history of an active game */
3825 			if (gameInfo.resultDetails != NULL) {
3826 			    free(gameInfo.resultDetails);
3827 			    gameInfo.resultDetails = NULL;
3828 			}
3829 		    }
3830 		    HistorySet(parseList, backwardMostMove,
3831 			       forwardMostMove, currentMove-1);
3832 		    DisplayMove(currentMove - 1);
3833 		    if (started == STARTED_MOVES) next_out = i;
3834 		    started = STARTED_NONE;
3835 		    ics_getting_history = H_FALSE;
3836 		    break;
3837 
3838 		  case STARTED_OBSERVE:
3839 		    started = STARTED_NONE;
3840 		    SendToICS(ics_prefix);
3841 		    SendToICS("refresh\n");
3842 		    break;
3843 
3844 		  default:
3845 		    break;
3846 		}
3847 		if(bookHit) { // [HGM] book: simulate book reply
3848 		    static char bookMove[MSG_SIZ]; // a bit generous?
3849 
3850 		    programStats.nodes = programStats.depth = programStats.time =
3851 		    programStats.score = programStats.got_only_move = 0;
3852 		    sprintf(programStats.movelist, "%s (xbook)", bookHit);
3853 
3854 		    safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3855 		    strcat(bookMove, bookHit);
3856 		    HandleMachineMove(bookMove, &first);
3857 		}
3858 		continue;
3859 	    }
3860 
3861 	    if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3862 		 started == STARTED_HOLDINGS ||
3863 		 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3864 		/* Accumulate characters in move list or board */
3865 		parse[parse_pos++] = buf[i];
3866 	    }
3867 
3868 	    /* Start of game messages.  Mostly we detect start of game
3869 	       when the first board image arrives.  On some versions
3870 	       of the ICS, though, we need to do a "refresh" after starting
3871 	       to observe in order to get the current board right away. */
3872 	    if (looking_at(buf, &i, "Adding game * to observation list")) {
3873 		started = STARTED_OBSERVE;
3874 		continue;
3875 	    }
3876 
3877 	    /* Handle auto-observe */
3878 	    if (appData.autoObserve &&
3879 		(gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3880 		looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3881 		char *player;
3882 		/* Choose the player that was highlighted, if any. */
3883 		if (star_match[0][0] == '\033' ||
3884 		    star_match[1][0] != '\033') {
3885 		    player = star_match[0];
3886 		} else {
3887 		    player = star_match[2];
3888 		}
3889 		snprintf(str, MSG_SIZ, "%sobserve %s\n",
3890 			ics_prefix, StripHighlightAndTitle(player));
3891 		SendToICS(str);
3892 
3893 		/* Save ratings from notify string */
3894 		safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3895 		player1Rating = string_to_rating(star_match[1]);
3896 		safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3897 		player2Rating = string_to_rating(star_match[3]);
3898 
3899 		if (appData.debugMode)
3900 		  fprintf(debugFP,
3901 			  "Ratings from 'Game notification:' %s %d, %s %d\n",
3902 			  player1Name, player1Rating,
3903 			  player2Name, player2Rating);
3904 
3905 		continue;
3906 	    }
3907 
3908 	    /* Deal with automatic examine mode after a game,
3909 	       and with IcsObserving -> IcsExamining transition */
3910 	    if (looking_at(buf, &i, "Entering examine mode for game *") ||
3911 		looking_at(buf, &i, "has made you an examiner of game *")) {
3912 
3913 		int gamenum = atoi(star_match[0]);
3914 		if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3915 		    gamenum == ics_gamenum) {
3916 		    /* We were already playing or observing this game;
3917 		       no need to refetch history */
3918 		    gameMode = IcsExamining;
3919 		    if (pausing) {
3920 			pauseExamForwardMostMove = forwardMostMove;
3921 		    } else if (currentMove < forwardMostMove) {
3922 			ForwardInner(forwardMostMove);
3923 		    }
3924 		} else {
3925 		    /* I don't think this case really can happen */
3926 		    SendToICS(ics_prefix);
3927 		    SendToICS("refresh\n");
3928 		}
3929 		continue;
3930 	    }
3931 
3932 	    /* Error messages */
3933 //	    if (ics_user_moved) {
3934 	    if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3935 		if (looking_at(buf, &i, "Illegal move") ||
3936 		    looking_at(buf, &i, "Not a legal move") ||
3937 		    looking_at(buf, &i, "Your king is in check") ||
3938 		    looking_at(buf, &i, "It isn't your turn") ||
3939 		    looking_at(buf, &i, "It is not your move")) {
3940 		    /* Illegal move */
3941 		    if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3942 			currentMove = forwardMostMove-1;
3943 			DisplayMove(currentMove - 1); /* before DMError */
3944 			DrawPosition(FALSE, boards[currentMove]);
3945 			SwitchClocks(forwardMostMove-1); // [HGM] race
3946 			DisplayBothClocks();
3947 		    }
3948 		    DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3949 		    ics_user_moved = 0;
3950 		    continue;
3951 		}
3952 	    }
3953 
3954 	    if (looking_at(buf, &i, "still have time") ||
3955 		looking_at(buf, &i, "not out of time") ||
3956 		looking_at(buf, &i, "either player is out of time") ||
3957 		looking_at(buf, &i, "has timeseal; checking")) {
3958 		/* We must have called his flag a little too soon */
3959 		whiteFlag = blackFlag = FALSE;
3960 		continue;
3961 	    }
3962 
3963 	    if (looking_at(buf, &i, "added * seconds to") ||
3964 		looking_at(buf, &i, "seconds were added to")) {
3965 		/* Update the clocks */
3966 		SendToICS(ics_prefix);
3967 		SendToICS("refresh\n");
3968 		continue;
3969 	    }
3970 
3971 	    if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3972 		ics_clock_paused = TRUE;
3973 		StopClocks();
3974 		continue;
3975 	    }
3976 
3977 	    if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3978 		ics_clock_paused = FALSE;
3979 		StartClocks();
3980 		continue;
3981 	    }
3982 
3983 	    /* Grab player ratings from the Creating: message.
3984 	       Note we have to check for the special case when
3985 	       the ICS inserts things like [white] or [black]. */
3986 	    if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3987 		looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3988 		/* star_matches:
3989 		   0    player 1 name (not necessarily white)
3990 		   1	player 1 rating
3991 		   2	empty, white, or black (IGNORED)
3992 		   3	player 2 name (not necessarily black)
3993 		   4    player 2 rating
3994 
3995 		   The names/ratings are sorted out when the game
3996 		   actually starts (below).
3997 		*/
3998 	        safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3999 	        player1Rating = string_to_rating(star_match[1]);
4000 	        safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4001 	        player2Rating = string_to_rating(star_match[4]);
4002 
4003 		if (appData.debugMode)
4004 		  fprintf(debugFP,
4005 			  "Ratings from 'Creating:' %s %d, %s %d\n",
4006 			  player1Name, player1Rating,
4007 			  player2Name, player2Rating);
4008 
4009 		continue;
4010 	    }
4011 
4012 	    /* Improved generic start/end-of-game messages */
4013 	    if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4014 		(tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4015 	        /* If tkind == 0: */
4016 		/* star_match[0] is the game number */
4017 		/*           [1] is the white player's name */
4018 		/*           [2] is the black player's name */
4019 		/* For end-of-game: */
4020 		/*           [3] is the reason for the game end */
4021 		/*           [4] is a PGN end game-token, preceded by " " */
4022 		/* For start-of-game: */
4023 		/*           [3] begins with "Creating" or "Continuing" */
4024 		/*           [4] is " *" or empty (don't care). */
4025 		int gamenum = atoi(star_match[0]);
4026 		char *whitename, *blackname, *why, *endtoken;
4027 		ChessMove endtype = EndOfFile;
4028 
4029 		if (tkind == 0) {
4030 		  whitename = star_match[1];
4031 		  blackname = star_match[2];
4032 		  why = star_match[3];
4033 		  endtoken = star_match[4];
4034 		} else {
4035 		  whitename = star_match[1];
4036 		  blackname = star_match[3];
4037 		  why = star_match[5];
4038 		  endtoken = star_match[6];
4039 		}
4040 
4041                 /* Game start messages */
4042 		if (strncmp(why, "Creating ", 9) == 0 ||
4043 		    strncmp(why, "Continuing ", 11) == 0) {
4044 		    gs_gamenum = gamenum;
4045 		    safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4046 		    if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4047 		    VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4048 #if ZIPPY
4049 		    if (appData.zippyPlay) {
4050 			ZippyGameStart(whitename, blackname);
4051 		    }
4052 #endif /*ZIPPY*/
4053 		    partnerBoardValid = FALSE; // [HGM] bughouse
4054 		    continue;
4055 		}
4056 
4057 		/* Game end messages */
4058 		if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4059 		    ics_gamenum != gamenum) {
4060 		    continue;
4061 		}
4062 		while (endtoken[0] == ' ') endtoken++;
4063 		switch (endtoken[0]) {
4064 		  case '*':
4065 		  default:
4066 		    endtype = GameUnfinished;
4067 		    break;
4068 		  case '0':
4069 		    endtype = BlackWins;
4070 		    break;
4071 		  case '1':
4072 		    if (endtoken[1] == '/')
4073 		      endtype = GameIsDrawn;
4074 		    else
4075 		      endtype = WhiteWins;
4076 		    break;
4077 		}
4078 		GameEnds(endtype, why, GE_ICS);
4079 #if ZIPPY
4080 		if (appData.zippyPlay && first.initDone) {
4081 		    ZippyGameEnd(endtype, why);
4082 		    if (first.pr == NoProc) {
4083 		      /* Start the next process early so that we'll
4084 			 be ready for the next challenge */
4085 		      StartChessProgram(&first);
4086 		    }
4087 		    /* Send "new" early, in case this command takes
4088 		       a long time to finish, so that we'll be ready
4089 		       for the next challenge. */
4090 		    gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4091 		    Reset(TRUE, TRUE);
4092 		}
4093 #endif /*ZIPPY*/
4094 		if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4095 		continue;
4096 	    }
4097 
4098 	    if (looking_at(buf, &i, "Removing game * from observation") ||
4099 		looking_at(buf, &i, "no longer observing game *") ||
4100 		looking_at(buf, &i, "Game * (*) has no examiners")) {
4101 		if (gameMode == IcsObserving &&
4102 		    atoi(star_match[0]) == ics_gamenum)
4103 		  {
4104                       /* icsEngineAnalyze */
4105                       if (appData.icsEngineAnalyze) {
4106                             ExitAnalyzeMode();
4107                             ModeHighlight();
4108                       }
4109 		      StopClocks();
4110 		      gameMode = IcsIdle;
4111 		      ics_gamenum = -1;
4112 		      ics_user_moved = FALSE;
4113 		  }
4114 		continue;
4115 	    }
4116 
4117 	    if (looking_at(buf, &i, "no longer examining game *")) {
4118 		if (gameMode == IcsExamining &&
4119 		    atoi(star_match[0]) == ics_gamenum)
4120 		  {
4121 		      gameMode = IcsIdle;
4122 		      ics_gamenum = -1;
4123 		      ics_user_moved = FALSE;
4124 		  }
4125 		continue;
4126 	    }
4127 
4128 	    /* Advance leftover_start past any newlines we find,
4129 	       so only partial lines can get reparsed */
4130 	    if (looking_at(buf, &i, "\n")) {
4131 		prevColor = curColor;
4132 		if (curColor != ColorNormal) {
4133 		    if (oldi > next_out) {
4134 			SendToPlayer(&buf[next_out], oldi - next_out);
4135 			next_out = oldi;
4136 		    }
4137 		    Colorize(ColorNormal, FALSE);
4138 		    curColor = ColorNormal;
4139 		}
4140 		if (started == STARTED_BOARD) {
4141 		    started = STARTED_NONE;
4142 		    parse[parse_pos] = NULLCHAR;
4143 		    ParseBoard12(parse);
4144 		    ics_user_moved = 0;
4145 
4146 		    /* Send premove here */
4147 		    if (appData.premove) {
4148 		      char str[MSG_SIZ];
4149 		      if (currentMove == 0 &&
4150 			  gameMode == IcsPlayingWhite &&
4151 			  appData.premoveWhite) {
4152 			snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4153 			if (appData.debugMode)
4154 			  fprintf(debugFP, "Sending premove:\n");
4155 			SendToICS(str);
4156 		      } else if (currentMove == 1 &&
4157 				 gameMode == IcsPlayingBlack &&
4158 				 appData.premoveBlack) {
4159 			snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4160 			if (appData.debugMode)
4161 			  fprintf(debugFP, "Sending premove:\n");
4162 			SendToICS(str);
4163 		      } else if (gotPremove) {
4164 			gotPremove = 0;
4165 			ClearPremoveHighlights();
4166 			if (appData.debugMode)
4167 			  fprintf(debugFP, "Sending premove:\n");
4168                           UserMoveEvent(premoveFromX, premoveFromY,
4169 				        premoveToX, premoveToY,
4170                                         premovePromoChar);
4171 		      }
4172 		    }
4173 
4174 		    /* Usually suppress following prompt */
4175 		    if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4176 			while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4177 			if (looking_at(buf, &i, "*% ")) {
4178 			    savingComment = FALSE;
4179 			    suppressKibitz = 0;
4180 			}
4181 		    }
4182 		    next_out = i;
4183 		} else if (started == STARTED_HOLDINGS) {
4184 		    int gamenum;
4185 		    char new_piece[MSG_SIZ];
4186 		    started = STARTED_NONE;
4187 		    parse[parse_pos] = NULLCHAR;
4188 		    if (appData.debugMode)
4189                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4190                                                         parse, currentMove);
4191 		    if (sscanf(parse, " game %d", &gamenum) == 1) {
4192 		      if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4193 		        if (gameInfo.variant == VariantNormal) {
4194                           /* [HGM] We seem to switch variant during a game!
4195                            * Presumably no holdings were displayed, so we have
4196                            * to move the position two files to the right to
4197                            * create room for them!
4198                            */
4199 			  VariantClass newVariant;
4200 			  switch(gameInfo.boardWidth) { // base guess on board width
4201 				case 9:  newVariant = VariantShogi; break;
4202 				case 10: newVariant = VariantGreat; break;
4203 				default: newVariant = VariantCrazyhouse; break;
4204 			  }
4205                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4206 			  /* Get a move list just to see the header, which
4207 			     will tell us whether this is really bug or zh */
4208 			  if (ics_getting_history == H_FALSE) {
4209 			    ics_getting_history = H_REQUESTED;
4210 			    snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4211 			    SendToICS(str);
4212 			  }
4213 			}
4214 			new_piece[0] = NULLCHAR;
4215 			sscanf(parse, "game %d white [%s black [%s <- %s",
4216 			       &gamenum, white_holding, black_holding,
4217 			       new_piece);
4218                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4219                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4220                         /* [HGM] copy holdings to board holdings area */
4221                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4222                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4223                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4224 #if ZIPPY
4225 			if (appData.zippyPlay && first.initDone) {
4226 			    ZippyHoldings(white_holding, black_holding,
4227 					  new_piece);
4228 			}
4229 #endif /*ZIPPY*/
4230 			if (tinyLayout || smallLayout) {
4231 			    char wh[16], bh[16];
4232 			    PackHolding(wh, white_holding);
4233 			    PackHolding(bh, black_holding);
4234 			    snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4235 				    gameInfo.white, gameInfo.black);
4236 			} else {
4237 			  snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4238 				    gameInfo.white, white_holding, _("vs."),
4239 				    gameInfo.black, black_holding);
4240 			}
4241 			if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4242                         DrawPosition(FALSE, boards[currentMove]);
4243 			DisplayTitle(str);
4244 		      } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4245 			sscanf(parse, "game %d white [%s black [%s <- %s",
4246 			       &gamenum, white_holding, black_holding,
4247 			       new_piece);
4248                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4249                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4250                         /* [HGM] copy holdings to partner-board holdings area */
4251                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4252                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4253                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4254                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4255                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4256 		      }
4257 		    }
4258 		    /* Suppress following prompt */
4259 		    if (looking_at(buf, &i, "*% ")) {
4260 			if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4261 			savingComment = FALSE;
4262 			suppressKibitz = 0;
4263 		    }
4264 		    next_out = i;
4265 		}
4266 		continue;
4267 	    }
4268 
4269 	    i++;		/* skip unparsed character and loop back */
4270 	}
4271 
4272 	if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4273 //	    started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4274 //	    SendToPlayer(&buf[next_out], i - next_out);
4275 	    started != STARTED_HOLDINGS && leftover_start > next_out) {
4276 	    SendToPlayer(&buf[next_out], leftover_start - next_out);
4277 	    next_out = i;
4278 	}
4279 
4280 	leftover_len = buf_len - leftover_start;
4281 	/* if buffer ends with something we couldn't parse,
4282 	   reparse it after appending the next read */
4283 
4284     } else if (count == 0) {
4285 	RemoveInputSource(isr);
4286         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4287     } else {
4288 	DisplayFatalError(_("Error reading from ICS"), error, 1);
4289     }
4290 }
4291 
4292 
4293 /* Board style 12 looks like this:
4294 
4295    <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
4296 
4297  * The "<12> " is stripped before it gets to this routine.  The two
4298  * trailing 0's (flip state and clock ticking) are later addition, and
4299  * some chess servers may not have them, or may have only the first.
4300  * Additional trailing fields may be added in the future.
4301  */
4302 
4303 #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"
4304 
4305 #define RELATION_OBSERVING_PLAYED    0
4306 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4307 #define RELATION_PLAYING_MYMOVE      1
4308 #define RELATION_PLAYING_NOTMYMOVE  -1
4309 #define RELATION_EXAMINING           2
4310 #define RELATION_ISOLATED_BOARD     -3
4311 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4312 
4313 void
ParseBoard12(char * string)4314 ParseBoard12 (char *string)
4315 {
4316 #if ZIPPY
4317     int i, takeback;
4318     char *bookHit = NULL; // [HGM] book
4319 #endif
4320     GameMode newGameMode;
4321     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4322     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4323     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4324     char to_play, board_chars[200];
4325     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4326     char black[32], white[32];
4327     Board board;
4328     int prevMove = currentMove;
4329     int ticking = 2;
4330     ChessMove moveType;
4331     int fromX, fromY, toX, toY;
4332     char promoChar;
4333     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4334     Boolean weird = FALSE, reqFlag = FALSE;
4335 
4336     fromX = fromY = toX = toY = -1;
4337 
4338     newGame = FALSE;
4339 
4340     if (appData.debugMode)
4341       fprintf(debugFP, "Parsing board: %s\n", string);
4342 
4343     move_str[0] = NULLCHAR;
4344     elapsed_time[0] = NULLCHAR;
4345     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4346         int  i = 0, j;
4347         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4348 	    if(string[i] == ' ') { ranks++; files = 0; }
4349             else files++;
4350 	    if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4351 	    i++;
4352 	}
4353 	for(j = 0; j <i; j++) board_chars[j] = string[j];
4354         board_chars[i] = '\0';
4355 	string += i + 1;
4356     }
4357     n = sscanf(string, PATTERN, &to_play, &double_push,
4358 	       &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4359 	       &gamenum, white, black, &relation, &basetime, &increment,
4360 	       &white_stren, &black_stren, &white_time, &black_time,
4361 	       &moveNum, str, elapsed_time, move_str, &ics_flip,
4362 	       &ticking);
4363 
4364     if (n < 21) {
4365         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4366 	DisplayError(str, 0);
4367 	return;
4368     }
4369 
4370     /* Convert the move number to internal form */
4371     moveNum = (moveNum - 1) * 2;
4372     if (to_play == 'B') moveNum++;
4373     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4374       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4375 			0, 1);
4376       return;
4377     }
4378 
4379     switch (relation) {
4380       case RELATION_OBSERVING_PLAYED:
4381       case RELATION_OBSERVING_STATIC:
4382 	if (gamenum == -1) {
4383 	    /* Old ICC buglet */
4384 	    relation = RELATION_OBSERVING_STATIC;
4385 	}
4386 	newGameMode = IcsObserving;
4387 	break;
4388       case RELATION_PLAYING_MYMOVE:
4389       case RELATION_PLAYING_NOTMYMOVE:
4390 	newGameMode =
4391 	  ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4392 	    IcsPlayingWhite : IcsPlayingBlack;
4393 	soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4394 	break;
4395       case RELATION_EXAMINING:
4396 	newGameMode = IcsExamining;
4397 	break;
4398       case RELATION_ISOLATED_BOARD:
4399       default:
4400 	/* Just display this board.  If user was doing something else,
4401 	   we will forget about it until the next board comes. */
4402 	newGameMode = IcsIdle;
4403 	break;
4404       case RELATION_STARTING_POSITION:
4405 	newGameMode = gameMode;
4406 	break;
4407     }
4408 
4409     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4410 	gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4411 	 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4412       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4413       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4414       static int lastBgGame = -1;
4415       char *toSqr;
4416       for (k = 0; k < ranks; k++) {
4417         for (j = 0; j < files; j++)
4418           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4419         if(gameInfo.holdingsWidth > 1) {
4420              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4421              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4422         }
4423       }
4424       CopyBoard(partnerBoard, board);
4425       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4426         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4427         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4428       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4429       if(toSqr = strchr(str, '-')) {
4430         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4431         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4432       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4433       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4434       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4435       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4436       if(twoBoards) {
4437 	  DisplayWhiteClock(white_time*fac, to_play == 'W');
4438 	  DisplayBlackClock(black_time*fac, to_play != 'W');
4439 	  activePartner = to_play;
4440 	  if(gamenum != lastBgGame) {
4441 	      char buf[MSG_SIZ];
4442 	      snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4443 	      DisplayTitle(buf);
4444 	  }
4445 	  lastBgGame = gamenum;
4446 	  activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4447 	              partnerUp = 0; flipView = !flipView; } // [HGM] dual
4448       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4449 		 (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4450       if(!twoBoards) DisplayMessage(partnerStatus, "");
4451 	partnerBoardValid = TRUE;
4452       return;
4453     }
4454 
4455     if(appData.dualBoard && appData.bgObserve) {
4456 	if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4457 	    SendToICS(ics_prefix), SendToICS("pobserve\n");
4458 	else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4459 	    char buf[MSG_SIZ];
4460 	    snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4461 	    SendToICS(buf);
4462 	}
4463     }
4464 
4465     /* Modify behavior for initial board display on move listing
4466        of wild games.
4467        */
4468     switch (ics_getting_history) {
4469       case H_FALSE:
4470       case H_REQUESTED:
4471 	break;
4472       case H_GOT_REQ_HEADER:
4473       case H_GOT_UNREQ_HEADER:
4474 	/* This is the initial position of the current game */
4475 	gamenum = ics_gamenum;
4476 	moveNum = 0;		/* old ICS bug workaround */
4477  	if (to_play == 'B') {
4478 	  startedFromSetupPosition = TRUE;
4479 	  blackPlaysFirst = TRUE;
4480 	  moveNum = 1;
4481 	  if (forwardMostMove == 0) forwardMostMove = 1;
4482 	  if (backwardMostMove == 0) backwardMostMove = 1;
4483 	  if (currentMove == 0) currentMove = 1;
4484 	}
4485 	newGameMode = gameMode;
4486 	relation = RELATION_STARTING_POSITION; /* ICC needs this */
4487 	break;
4488       case H_GOT_UNWANTED_HEADER:
4489 	/* This is an initial board that we don't want */
4490 	return;
4491       case H_GETTING_MOVES:
4492 	/* Should not happen */
4493 	DisplayError(_("Error gathering move list: extra board"), 0);
4494 	ics_getting_history = H_FALSE;
4495 	return;
4496     }
4497 
4498    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4499 					move_str[1] == '@' && !gameInfo.holdingsWidth ||
4500 					weird && (int)gameInfo.variant < (int)VariantShogi) {
4501      /* [HGM] We seem to have switched variant unexpectedly
4502       * Try to guess new variant from board size
4503       */
4504 	  VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4505 	  if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4506 	  if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4507 	  if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4508 	  if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4509 	  if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4510 	  if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4511           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4512 	  /* Get a move list just to see the header, which
4513 	     will tell us whether this is really bug or zh */
4514 	  if (ics_getting_history == H_FALSE) {
4515 	    ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4516 	    snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4517 	    SendToICS(str);
4518 	  }
4519     }
4520 
4521     /* Take action if this is the first board of a new game, or of a
4522        different game than is currently being displayed.  */
4523     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4524 	relation == RELATION_ISOLATED_BOARD) {
4525 
4526 	/* Forget the old game and get the history (if any) of the new one */
4527 	if (gameMode != BeginningOfGame) {
4528 	  Reset(TRUE, TRUE);
4529 	}
4530 	newGame = TRUE;
4531 	if (appData.autoRaiseBoard) BoardToTop();
4532 	prevMove = -3;
4533 	if (gamenum == -1) {
4534 	    newGameMode = IcsIdle;
4535 	} else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4536 		   appData.getMoveList && !reqFlag) {
4537 	    /* Need to get game history */
4538 	    ics_getting_history = H_REQUESTED;
4539 	    snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4540 	    SendToICS(str);
4541 	}
4542 
4543 	/* Initially flip the board to have black on the bottom if playing
4544 	   black or if the ICS flip flag is set, but let the user change
4545 	   it with the Flip View button. */
4546 	flipView = appData.autoFlipView ?
4547 	  (newGameMode == IcsPlayingBlack) || ics_flip :
4548 	  appData.flipView;
4549 
4550 	/* Done with values from previous mode; copy in new ones */
4551 	gameMode = newGameMode;
4552 	ModeHighlight();
4553 	ics_gamenum = gamenum;
4554 	if (gamenum == gs_gamenum) {
4555 	    int klen = strlen(gs_kind);
4556 	    if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4557 	    snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4558 	    gameInfo.event = StrSave(str);
4559 	} else {
4560 	    gameInfo.event = StrSave("ICS game");
4561 	}
4562 	gameInfo.site = StrSave(appData.icsHost);
4563 	gameInfo.date = PGNDate();
4564 	gameInfo.round = StrSave("-");
4565 	gameInfo.white = StrSave(white);
4566 	gameInfo.black = StrSave(black);
4567 	timeControl = basetime * 60 * 1000;
4568         timeControl_2 = 0;
4569 	timeIncrement = increment * 1000;
4570 	movesPerSession = 0;
4571 	gameInfo.timeControl = TimeControlTagValue();
4572         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4573   if (appData.debugMode) {
4574     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4575     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4576     setbuf(debugFP, NULL);
4577   }
4578 
4579         gameInfo.outOfBook = NULL;
4580 
4581 	/* Do we have the ratings? */
4582 	if (strcmp(player1Name, white) == 0 &&
4583 	    strcmp(player2Name, black) == 0) {
4584 	    if (appData.debugMode)
4585 	      fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4586 		      player1Rating, player2Rating);
4587 	    gameInfo.whiteRating = player1Rating;
4588 	    gameInfo.blackRating = player2Rating;
4589 	} else if (strcmp(player2Name, white) == 0 &&
4590 		   strcmp(player1Name, black) == 0) {
4591 	    if (appData.debugMode)
4592 	      fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4593 		      player2Rating, player1Rating);
4594 	    gameInfo.whiteRating = player2Rating;
4595 	    gameInfo.blackRating = player1Rating;
4596 	}
4597 	player1Name[0] = player2Name[0] = NULLCHAR;
4598 
4599 	/* Silence shouts if requested */
4600 	if (appData.quietPlay &&
4601 	    (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4602 	    SendToICS(ics_prefix);
4603 	    SendToICS("set shout 0\n");
4604 	}
4605     }
4606 
4607     /* Deal with midgame name changes */
4608     if (!newGame) {
4609 	if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4610 	    if (gameInfo.white) free(gameInfo.white);
4611 	    gameInfo.white = StrSave(white);
4612 	}
4613 	if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4614 	    if (gameInfo.black) free(gameInfo.black);
4615 	    gameInfo.black = StrSave(black);
4616 	}
4617     }
4618 
4619     /* Throw away game result if anything actually changes in examine mode */
4620     if (gameMode == IcsExamining && !newGame) {
4621 	gameInfo.result = GameUnfinished;
4622 	if (gameInfo.resultDetails != NULL) {
4623 	    free(gameInfo.resultDetails);
4624 	    gameInfo.resultDetails = NULL;
4625 	}
4626     }
4627 
4628     /* In pausing && IcsExamining mode, we ignore boards coming
4629        in if they are in a different variation than we are. */
4630     if (pauseExamInvalid) return;
4631     if (pausing && gameMode == IcsExamining) {
4632 	if (moveNum <= pauseExamForwardMostMove) {
4633 	    pauseExamInvalid = TRUE;
4634 	    forwardMostMove = pauseExamForwardMostMove;
4635 	    return;
4636 	}
4637     }
4638 
4639   if (appData.debugMode) {
4640     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4641   }
4642     /* Parse the board */
4643     for (k = 0; k < ranks; k++) {
4644       for (j = 0; j < files; j++)
4645         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4646       if(gameInfo.holdingsWidth > 1) {
4647            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4648            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4649       }
4650     }
4651     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4652       board[5][BOARD_RGHT+1] = WhiteAngel;
4653       board[6][BOARD_RGHT+1] = WhiteMarshall;
4654       board[1][0] = BlackMarshall;
4655       board[2][0] = BlackAngel;
4656       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4657     }
4658     CopyBoard(boards[moveNum], board);
4659     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4660     if (moveNum == 0) {
4661 	startedFromSetupPosition =
4662 	  !CompareBoards(board, initialPosition);
4663         if(startedFromSetupPosition)
4664             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4665     }
4666 
4667     /* [HGM] Set castling rights. Take the outermost Rooks,
4668        to make it also work for FRC opening positions. Note that board12
4669        is really defective for later FRC positions, as it has no way to
4670        indicate which Rook can castle if they are on the same side of King.
4671        For the initial position we grant rights to the outermost Rooks,
4672        and remember thos rights, and we then copy them on positions
4673        later in an FRC game. This means WB might not recognize castlings with
4674        Rooks that have moved back to their original position as illegal,
4675        but in ICS mode that is not its job anyway.
4676     */
4677     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4678     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4679 
4680         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4681             if(board[0][i] == WhiteRook) j = i;
4682         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4683         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4684             if(board[0][i] == WhiteRook) j = i;
4685         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4686         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4687             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4688         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4689         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4690             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4691         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4692 
4693 	boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4694 	if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4698             if(board[BOARD_HEIGHT-1][k] == bKing)
4699                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4700         if(gameInfo.variant == VariantTwoKings) {
4701             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4702             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4703             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4704         }
4705     } else { int r;
4706         r = boards[moveNum][CASTLING][0] = initialRights[0];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4708         r = boards[moveNum][CASTLING][1] = initialRights[1];
4709         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4710         r = boards[moveNum][CASTLING][3] = initialRights[3];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4712         r = boards[moveNum][CASTLING][4] = initialRights[4];
4713         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4714         /* wildcastle kludge: always assume King has rights */
4715         r = boards[moveNum][CASTLING][2] = initialRights[2];
4716         r = boards[moveNum][CASTLING][5] = initialRights[5];
4717     }
4718     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4719     boards[moveNum][EP_STATUS] = EP_NONE;
4720     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4721     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4722     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4723 
4724 
4725     if (ics_getting_history == H_GOT_REQ_HEADER ||
4726 	ics_getting_history == H_GOT_UNREQ_HEADER) {
4727 	/* This was an initial position from a move list, not
4728 	   the current position */
4729 	return;
4730     }
4731 
4732     /* Update currentMove and known move number limits */
4733     newMove = newGame || moveNum > forwardMostMove;
4734 
4735     if (newGame) {
4736 	forwardMostMove = backwardMostMove = currentMove = moveNum;
4737 	if (gameMode == IcsExamining && moveNum == 0) {
4738 	  /* Workaround for ICS limitation: we are not told the wild
4739 	     type when starting to examine a game.  But if we ask for
4740 	     the move list, the move list header will tell us */
4741 	    ics_getting_history = H_REQUESTED;
4742 	    snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4743 	    SendToICS(str);
4744 	}
4745     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4746 	       || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4747 #if ZIPPY
4748 	/* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4749 	/* [HGM] applied this also to an engine that is silently watching        */
4750 	if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4751 	    (gameMode == IcsObserving || gameMode == IcsExamining) &&
4752 	    gameInfo.variant == currentlyInitializedVariant) {
4753 	  takeback = forwardMostMove - moveNum;
4754 	  for (i = 0; i < takeback; i++) {
4755 	    if (appData.debugMode) fprintf(debugFP, "take back move\n");
4756 	    SendToProgram("undo\n", &first);
4757 	  }
4758 	}
4759 #endif
4760 
4761 	forwardMostMove = moveNum;
4762 	if (!pausing || currentMove > forwardMostMove)
4763 	  currentMove = forwardMostMove;
4764     } else {
4765 	/* New part of history that is not contiguous with old part */
4766 	if (pausing && gameMode == IcsExamining) {
4767 	    pauseExamInvalid = TRUE;
4768 	    forwardMostMove = pauseExamForwardMostMove;
4769 	    return;
4770 	}
4771 	if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4772 #if ZIPPY
4773 	    if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4774 		// [HGM] when we will receive the move list we now request, it will be
4775 		// fed to the engine from the first move on. So if the engine is not
4776 		// in the initial position now, bring it there.
4777 		InitChessProgram(&first, 0);
4778 	    }
4779 #endif
4780 	    ics_getting_history = H_REQUESTED;
4781 	    snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4782 	    SendToICS(str);
4783 	}
4784 	forwardMostMove = backwardMostMove = currentMove = moveNum;
4785     }
4786 
4787     /* Update the clocks */
4788     if (strchr(elapsed_time, '.')) {
4789       /* Time is in ms */
4790       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4791       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4792     } else {
4793       /* Time is in seconds */
4794       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4795       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4796     }
4797 
4798 
4799 #if ZIPPY
4800     if (appData.zippyPlay && newGame &&
4801 	gameMode != IcsObserving && gameMode != IcsIdle &&
4802 	gameMode != IcsExamining)
4803       ZippyFirstBoard(moveNum, basetime, increment);
4804 #endif
4805 
4806     /* Put the move on the move list, first converting
4807        to canonical algebraic form. */
4808     if (moveNum > 0) {
4809   if (appData.debugMode) {
4810     int f = forwardMostMove;
4811     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4812 	    boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4813 	    boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4814     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4815     fprintf(debugFP, "moveNum = %d\n", moveNum);
4816     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4817     setbuf(debugFP, NULL);
4818   }
4819 	if (moveNum <= backwardMostMove) {
4820 	    /* We don't know what the board looked like before
4821 	       this move.  Punt. */
4822 	  safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4823 	    strcat(parseList[moveNum - 1], " ");
4824 	    strcat(parseList[moveNum - 1], elapsed_time);
4825 	    moveList[moveNum - 1][0] = NULLCHAR;
4826 	} else if (strcmp(move_str, "none") == 0) {
4827 	    // [HGM] long SAN: swapped order; test for 'none' before parsing move
4828 	    /* Again, we don't know what the board looked like;
4829 	       this is really the start of the game. */
4830 	    parseList[moveNum - 1][0] = NULLCHAR;
4831 	    moveList[moveNum - 1][0] = NULLCHAR;
4832 	    backwardMostMove = moveNum;
4833 	    startedFromSetupPosition = TRUE;
4834  	    fromX = fromY = toX = toY = -1;
4835 	} else {
4836 	  // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4837 	  //                 So we parse the long-algebraic move string in stead of the SAN move
4838 	  int valid; char buf[MSG_SIZ], *prom;
4839 
4840 	  if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4841 		strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4842 	  // str looks something like "Q/a1-a2"; kill the slash
4843 	  if(str[1] == '/')
4844 	    snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4845 	  else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4846 	  if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4847 		strcat(buf, prom); // long move lacks promo specification!
4848 	  if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4849 		if(appData.debugMode)
4850 			fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4851 		safeStrCpy(move_str, buf, MSG_SIZ);
4852           }
4853 	  valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4854 				&fromX, &fromY, &toX, &toY, &promoChar)
4855 	       || ParseOneMove(buf, moveNum - 1, &moveType,
4856 				&fromX, &fromY, &toX, &toY, &promoChar);
4857 	  // end of long SAN patch
4858 	  if (valid) {
4859 	    (void) CoordsToAlgebraic(boards[moveNum - 1],
4860 				     PosFlags(moveNum - 1),
4861 				     fromY, fromX, toY, toX, promoChar,
4862 				     parseList[moveNum-1]);
4863             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4864 	      case MT_NONE:
4865 	      case MT_STALEMATE:
4866 	      default:
4867 		break;
4868 	      case MT_CHECK:
4869                 if(!IS_SHOGI(gameInfo.variant))
4870                     strcat(parseList[moveNum - 1], "+");
4871 		break;
4872 	      case MT_CHECKMATE:
4873 	      case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4874 		strcat(parseList[moveNum - 1], "#");
4875 		break;
4876 	    }
4877 	    strcat(parseList[moveNum - 1], " ");
4878 	    strcat(parseList[moveNum - 1], elapsed_time);
4879 	    /* currentMoveString is set as a side-effect of ParseOneMove */
4880 	    if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4881 	    safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4882 	    strcat(moveList[moveNum - 1], "\n");
4883 
4884             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4885                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4886               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4887                 ChessSquare old, new = boards[moveNum][k][j];
4888                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4889                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4890                   if(old == new) continue;
4891                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4892                   else if(new == WhiteWazir || new == BlackWazir) {
4893                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4894                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4895                       else boards[moveNum][k][j] = old; // preserve type of Gold
4896                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4897                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4898               }
4899 	  } else {
4900 	    /* Move from ICS was illegal!?  Punt. */
4901 	    if (appData.debugMode) {
4902 	      fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4903 	      fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4904 	    }
4905 	    safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4906 	    strcat(parseList[moveNum - 1], " ");
4907 	    strcat(parseList[moveNum - 1], elapsed_time);
4908 	    moveList[moveNum - 1][0] = NULLCHAR;
4909  	    fromX = fromY = toX = toY = -1;
4910 	  }
4911 	}
4912   if (appData.debugMode) {
4913     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4914     setbuf(debugFP, NULL);
4915   }
4916 
4917 #if ZIPPY
4918 	/* Send move to chess program (BEFORE animating it). */
4919 	if (appData.zippyPlay && !newGame && newMove &&
4920 	   (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4921 
4922 	    if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4923 		(gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4924 		if (moveList[moveNum - 1][0] == NULLCHAR) {
4925 		  snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4926 			    move_str);
4927 		    DisplayError(str, 0);
4928 		} else {
4929 		    if (first.sendTime) {
4930 			SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4931 		    }
4932 		    bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4933 		    if (firstMove && !bookHit) {
4934 			firstMove = FALSE;
4935 			if (first.useColors) {
4936 			  SendToProgram(gameMode == IcsPlayingWhite ?
4937 					"white\ngo\n" :
4938 					"black\ngo\n", &first);
4939 			} else {
4940 			  SendToProgram("go\n", &first);
4941 			}
4942 			first.maybeThinking = TRUE;
4943 		    }
4944 		}
4945 	    } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4946 	      if (moveList[moveNum - 1][0] == NULLCHAR) {
4947 		snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4948 		DisplayError(str, 0);
4949 	      } else {
4950 		if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4951 		SendMoveToProgram(moveNum - 1, &first);
4952 	      }
4953 	    }
4954 	}
4955 #endif
4956     }
4957 
4958     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4959 	/* If move comes from a remote source, animate it.  If it
4960 	   isn't remote, it will have already been animated. */
4961 	if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4962 	    AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4963 	}
4964 	if (!pausing && appData.highlightLastMove) {
4965 	    SetHighlights(fromX, fromY, toX, toY);
4966 	}
4967     }
4968 
4969     /* Start the clocks */
4970     whiteFlag = blackFlag = FALSE;
4971     appData.clockMode = !(basetime == 0 && increment == 0);
4972     if (ticking == 0) {
4973       ics_clock_paused = TRUE;
4974       StopClocks();
4975     } else if (ticking == 1) {
4976       ics_clock_paused = FALSE;
4977     }
4978     if (gameMode == IcsIdle ||
4979 	relation == RELATION_OBSERVING_STATIC ||
4980 	relation == RELATION_EXAMINING ||
4981 	ics_clock_paused)
4982       DisplayBothClocks();
4983     else
4984       StartClocks();
4985 
4986     /* Display opponents and material strengths */
4987     if (gameInfo.variant != VariantBughouse &&
4988 	gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4989 	if (tinyLayout || smallLayout) {
4990 	    if(gameInfo.variant == VariantNormal)
4991 	      snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4992 		    gameInfo.white, white_stren, gameInfo.black, black_stren,
4993 		    basetime, increment);
4994 	    else
4995 	      snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4996 		    gameInfo.white, white_stren, gameInfo.black, black_stren,
4997 		    basetime, increment, (int) gameInfo.variant);
4998 	} else {
4999 	    if(gameInfo.variant == VariantNormal)
5000 	      snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5001 		    gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5002 		    basetime, increment);
5003 	    else
5004 	      snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5005 		    gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5006 		    basetime, increment, VariantName(gameInfo.variant));
5007 	}
5008 	DisplayTitle(str);
5009   if (appData.debugMode) {
5010     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5011   }
5012     }
5013 
5014 
5015     /* Display the board */
5016     if (!pausing && !appData.noGUI) {
5017 
5018       if (appData.premove)
5019 	  if (!gotPremove ||
5020 	     ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5021 	     ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5022 	      ClearPremoveHighlights();
5023 
5024       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5025 	if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5026       DrawPosition(j, boards[currentMove]);
5027 
5028       DisplayMove(moveNum - 1);
5029       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5030 	    !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5031 	      (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5032 	if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5033       }
5034     }
5035 
5036     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5037 #if ZIPPY
5038     if(bookHit) { // [HGM] book: simulate book reply
5039 	static char bookMove[MSG_SIZ]; // a bit generous?
5040 
5041 	programStats.nodes = programStats.depth = programStats.time =
5042 	programStats.score = programStats.got_only_move = 0;
5043 	sprintf(programStats.movelist, "%s (xbook)", bookHit);
5044 
5045 	safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5046 	strcat(bookMove, bookHit);
5047 	HandleMachineMove(bookMove, &first);
5048     }
5049 #endif
5050 }
5051 
5052 void
GetMoveListEvent()5053 GetMoveListEvent ()
5054 {
5055     char buf[MSG_SIZ];
5056     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5057 	ics_getting_history = H_REQUESTED;
5058 	snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5059 	SendToICS(buf);
5060     }
5061 }
5062 
5063 void
SendToBoth(char * msg)5064 SendToBoth (char *msg)
5065 {   // to make it easy to keep two engines in step in dual analysis
5066     SendToProgram(msg, &first);
5067     if(second.analyzing) SendToProgram(msg, &second);
5068 }
5069 
5070 void
AnalysisPeriodicEvent(int force)5071 AnalysisPeriodicEvent (int force)
5072 {
5073     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5074 	 && !force) || !appData.periodicUpdates)
5075       return;
5076 
5077     /* Send . command to Crafty to collect stats */
5078     SendToBoth(".\n");
5079 
5080     /* Don't send another until we get a response (this makes
5081        us stop sending to old Crafty's which don't understand
5082        the "." command (sending illegal cmds resets node count & time,
5083        which looks bad)) */
5084     programStats.ok_to_send = 0;
5085 }
5086 
5087 void
ics_update_width(int new_width)5088 ics_update_width (int new_width)
5089 {
5090 	ics_printf("set width %d\n", new_width);
5091 }
5092 
5093 void
SendMoveToProgram(int moveNum,ChessProgramState * cps)5094 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5095 {
5096     char buf[MSG_SIZ];
5097 
5098     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5099 	if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5100 	    sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5101 	    SendToProgram(buf, cps);
5102 	    return;
5103 	}
5104 	// null move in variant where engine does not understand it (for analysis purposes)
5105 	SendBoard(cps, moveNum + 1); // send position after move in stead.
5106 	return;
5107     }
5108     if (cps->useUsermove) {
5109       SendToProgram("usermove ", cps);
5110     }
5111     if (cps->useSAN) {
5112       char *space;
5113       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5114 	int len = space - parseList[moveNum];
5115 	memcpy(buf, parseList[moveNum], len);
5116 	buf[len++] = '\n';
5117 	buf[len] = NULLCHAR;
5118       } else {
5119 	snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5120       }
5121       SendToProgram(buf, cps);
5122     } else {
5123       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5124 	AlphaRank(moveList[moveNum], 4);
5125 	SendToProgram(moveList[moveNum], cps);
5126 	AlphaRank(moveList[moveNum], 4); // and back
5127       } else
5128       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5129        * the engine. It would be nice to have a better way to identify castle
5130        * moves here. */
5131       if(appData.fischerCastling && cps->useOOCastle) {
5132         int fromX = moveList[moveNum][0] - AAA;
5133         int fromY = moveList[moveNum][1] - ONE;
5134         int toX = moveList[moveNum][2] - AAA;
5135         int toY = moveList[moveNum][3] - ONE;
5136         if((boards[moveNum][fromY][fromX] == WhiteKing
5137             && boards[moveNum][toY][toX] == WhiteRook)
5138            || (boards[moveNum][fromY][fromX] == BlackKing
5139                && boards[moveNum][toY][toX] == BlackRook)) {
5140 	  if(toX > fromX) SendToProgram("O-O\n", cps);
5141 	  else SendToProgram("O-O-O\n", cps);
5142 	}
5143 	else SendToProgram(moveList[moveNum], cps);
5144       } else
5145       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5146 	char *m = moveList[moveNum];
5147 	if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5148 	  snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5149 					       m[2], m[3] - '0',
5150 					       m[5], m[6] - '0',
5151 					       m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5152 	else
5153 	  snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5154 					       m[5], m[6] - '0',
5155 					       m[5], m[6] - '0',
5156 					       m[2], m[3] - '0');
5157 	  SendToProgram(buf, cps);
5158       } else
5159       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5160 	if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5161 	  if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5162 	  snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5163 					      moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5164 	} else
5165 	  snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5166 					       moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5167 	SendToProgram(buf, cps);
5168       }
5169       else SendToProgram(moveList[moveNum], cps);
5170       /* End of additions by Tord */
5171     }
5172 
5173     /* [HGM] setting up the opening has brought engine in force mode! */
5174     /*       Send 'go' if we are in a mode where machine should play. */
5175     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5176         (gameMode == TwoMachinesPlay   ||
5177 #if ZIPPY
5178          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5179 #endif
5180          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5181         SendToProgram("go\n", cps);
5182   if (appData.debugMode) {
5183     fprintf(debugFP, "(extra)\n");
5184   }
5185     }
5186     setboardSpoiledMachineBlack = 0;
5187 }
5188 
5189 void
SendMoveToICS(ChessMove moveType,int fromX,int fromY,int toX,int toY,char promoChar)5190 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5191 {
5192     char user_move[MSG_SIZ];
5193     char suffix[4];
5194 
5195     if(gameInfo.variant == VariantSChess && promoChar) {
5196 	snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5197 	if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5198     } else suffix[0] = NULLCHAR;
5199 
5200     switch (moveType) {
5201       default:
5202 	snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5203 		(int)moveType, fromX, fromY, toX, toY);
5204 	DisplayError(user_move + strlen("say "), 0);
5205 	break;
5206       case WhiteKingSideCastle:
5207       case BlackKingSideCastle:
5208       case WhiteQueenSideCastleWild:
5209       case BlackQueenSideCastleWild:
5210       /* PUSH Fabien */
5211       case WhiteHSideCastleFR:
5212       case BlackHSideCastleFR:
5213       /* POP Fabien */
5214 	snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5215 	break;
5216       case WhiteQueenSideCastle:
5217       case BlackQueenSideCastle:
5218       case WhiteKingSideCastleWild:
5219       case BlackKingSideCastleWild:
5220       /* PUSH Fabien */
5221       case WhiteASideCastleFR:
5222       case BlackASideCastleFR:
5223       /* POP Fabien */
5224 	snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5225 	break;
5226       case WhiteNonPromotion:
5227       case BlackNonPromotion:
5228         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5229         break;
5230       case WhitePromotion:
5231       case BlackPromotion:
5232         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5233            gameInfo.variant == VariantMakruk)
5234 	  snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5235                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5236 		PieceToChar(WhiteFerz));
5237         else if(gameInfo.variant == VariantGreat)
5238 	  snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5239                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5240 		PieceToChar(WhiteMan));
5241         else
5242 	  snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5243                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5244 		promoChar);
5245 	break;
5246       case WhiteDrop:
5247       case BlackDrop:
5248       drop:
5249 	snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5250 		 ToUpper(PieceToChar((ChessSquare) fromX)),
5251 		 AAA + toX, ONE + toY);
5252 	break;
5253       case IllegalMove:  /* could be a variant we don't quite understand */
5254         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5255       case NormalMove:
5256       case WhiteCapturesEnPassant:
5257       case BlackCapturesEnPassant:
5258 	snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5259                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5260 	break;
5261     }
5262     SendToICS(user_move);
5263     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5264 	ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5265 }
5266 
5267 void
UploadGameEvent()5268 UploadGameEvent ()
5269 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5270     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5271     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5272     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5273       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5274       return;
5275     }
5276     if(gameMode != IcsExamining) { // is this ever not the case?
5277 	char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5278 
5279 	if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5280 	  snprintf(command,MSG_SIZ, "match %s", ics_handle);
5281 	} else { // on FICS we must first go to general examine mode
5282 	  safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5283 	}
5284 	if(gameInfo.variant != VariantNormal) {
5285 	    // try figure out wild number, as xboard names are not always valid on ICS
5286 	    for(i=1; i<=36; i++) {
5287 	      snprintf(buf, MSG_SIZ, "wild/%d", i);
5288 		if(StringToVariant(buf) == gameInfo.variant) break;
5289 	    }
5290 	    if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5291 	    else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5292 	    else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5293 	} else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5294 	SendToICS(ics_prefix);
5295 	SendToICS(buf);
5296 	if(startedFromSetupPosition || backwardMostMove != 0) {
5297 	  fen = PositionToFEN(backwardMostMove, NULL, 1);
5298 	  if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5299 	    snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5300 	    SendToICS(buf);
5301 	  } else { // FICS: everything has to set by separate bsetup commands
5302 	    p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5303 	    snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5304 	    SendToICS(buf);
5305 	    if(!WhiteOnMove(backwardMostMove)) {
5306 		SendToICS("bsetup tomove black\n");
5307 	    }
5308 	    i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5309 	    snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5310 	    SendToICS(buf);
5311 	    i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5312 	    snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5313 	    SendToICS(buf);
5314 	    i = boards[backwardMostMove][EP_STATUS];
5315 	    if(i >= 0) { // set e.p.
5316 	      snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5317 		SendToICS(buf);
5318 	    }
5319 	    bsetup++;
5320 	  }
5321 	}
5322       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5323 	    SendToICS("bsetup done\n"); // switch to normal examining.
5324     }
5325     for(i = backwardMostMove; i<last; i++) {
5326 	char buf[20];
5327 	snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5328 	if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5329 	    int len = strlen(moveList[i]);
5330 	    snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5331 	    if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5332 	}
5333 	SendToICS(buf);
5334     }
5335     SendToICS(ics_prefix);
5336     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5337 }
5338 
5339 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5340 int legNr = 1;
5341 
5342 void
CoordsToComputerAlgebraic(int rf,int ff,int rt,int ft,char promoChar,char move[9])5343 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5344 {
5345     if (rf == DROP_RANK) {
5346       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5347       sprintf(move, "%c@%c%c\n",
5348                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5349     } else {
5350 	if (promoChar == 'x' || promoChar == NULLCHAR) {
5351 	  sprintf(move, "%c%c%c%c\n",
5352                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5353 	  if(killX >= 0 && killY >= 0) {
5354 	    sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5355 	    if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5356 	  }
5357 	} else {
5358 	    sprintf(move, "%c%c%c%c%c\n",
5359                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5360 	}
5361     }
5362 }
5363 
5364 void
ProcessICSInitScript(FILE * f)5365 ProcessICSInitScript (FILE *f)
5366 {
5367     char buf[MSG_SIZ];
5368 
5369     while (fgets(buf, MSG_SIZ, f)) {
5370 	SendToICSDelayed(buf,(long)appData.msLoginDelay);
5371     }
5372 
5373     fclose(f);
5374 }
5375 
5376 
5377 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5378 int dragging;
5379 static ClickType lastClickType;
5380 
5381 int
Partner(ChessSquare * p)5382 Partner (ChessSquare *p)
5383 { // change piece into promotion partner if one shogi-promotes to the other
5384   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5385   ChessSquare partner;
5386   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5387   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5388   *p = partner;
5389   return 1;
5390 }
5391 
5392 void
Sweep(int step)5393 Sweep (int step)
5394 {
5395     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5396     static int toggleFlag;
5397     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5398     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5399     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5400     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5401     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5402     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5403     do {
5404 	if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5405 	if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5406 	else if((int)promoSweep == -1) promoSweep = WhiteKing;
5407 	else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5408 	else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5409 	if(!step) step = -1;
5410     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5411 	    !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5412 	    appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5413             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5414     if(toX >= 0) {
5415 	int victim = boards[currentMove][toY][toX];
5416 	boards[currentMove][toY][toX] = promoSweep;
5417 	DrawPosition(FALSE, boards[currentMove]);
5418 	boards[currentMove][toY][toX] = victim;
5419     } else
5420     ChangeDragPiece(promoSweep);
5421 }
5422 
5423 int
PromoScroll(int x,int y)5424 PromoScroll (int x, int y)
5425 {
5426   int step = 0;
5427 
5428   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5429   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5430   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5431   if(!step) return FALSE;
5432   lastX = x; lastY = y;
5433   if((promoSweep < BlackPawn) == flipView) step = -step;
5434   if(step > 0) selectFlag = 1;
5435   if(!selectFlag) Sweep(step);
5436   return FALSE;
5437 }
5438 
5439 void
NextPiece(int step)5440 NextPiece (int step)
5441 {
5442     ChessSquare piece = boards[currentMove][toY][toX];
5443     do {
5444 	pieceSweep -= step;
5445 	if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5446 	if((int)pieceSweep == -1) pieceSweep = BlackKing;
5447 	if(!step) step = -1;
5448     } while(PieceToChar(pieceSweep) == '.');
5449     boards[currentMove][toY][toX] = pieceSweep;
5450     DrawPosition(FALSE, boards[currentMove]);
5451     boards[currentMove][toY][toX] = piece;
5452 }
5453 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5454 void
AlphaRank(char * move,int n)5455 AlphaRank (char *move, int n)
5456 {
5457 //    char *p = move, c; int x, y;
5458 
5459     if (appData.debugMode) {
5460         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5461     }
5462 
5463     if(move[1]=='*' &&
5464        move[2]>='0' && move[2]<='9' &&
5465        move[3]>='a' && move[3]<='x'    ) {
5466         move[1] = '@';
5467         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5468         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5469     } else
5470     if(move[0]>='0' && move[0]<='9' &&
5471        move[1]>='a' && move[1]<='x' &&
5472        move[2]>='0' && move[2]<='9' &&
5473        move[3]>='a' && move[3]<='x'    ) {
5474         /* input move, Shogi -> normal */
5475         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5476         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5477         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5478         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5479     } else
5480     if(move[1]=='@' &&
5481        move[3]>='0' && move[3]<='9' &&
5482        move[2]>='a' && move[2]<='x'    ) {
5483         move[1] = '*';
5484         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5485         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5486     } else
5487     if(
5488        move[0]>='a' && move[0]<='x' &&
5489        move[3]>='0' && move[3]<='9' &&
5490        move[2]>='a' && move[2]<='x'    ) {
5491          /* output move, normal -> Shogi */
5492         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5493         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5494         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5495         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5496         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5497     }
5498     if (appData.debugMode) {
5499         fprintf(debugFP, "   out = '%s'\n", move);
5500     }
5501 }
5502 
5503 char yy_textstr[8000];
5504 
5505 /* Parser for moves from gnuchess, ICS, or user typein box */
5506 Boolean
ParseOneMove(char * move,int moveNum,ChessMove * moveType,int * fromX,int * fromY,int * toX,int * toY,char * promoChar)5507 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5508 {
5509     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5510 
5511     switch (*moveType) {
5512       case WhitePromotion:
5513       case BlackPromotion:
5514       case WhiteNonPromotion:
5515       case BlackNonPromotion:
5516       case NormalMove:
5517       case FirstLeg:
5518       case WhiteCapturesEnPassant:
5519       case BlackCapturesEnPassant:
5520       case WhiteKingSideCastle:
5521       case WhiteQueenSideCastle:
5522       case BlackKingSideCastle:
5523       case BlackQueenSideCastle:
5524       case WhiteKingSideCastleWild:
5525       case WhiteQueenSideCastleWild:
5526       case BlackKingSideCastleWild:
5527       case BlackQueenSideCastleWild:
5528       /* Code added by Tord: */
5529       case WhiteHSideCastleFR:
5530       case WhiteASideCastleFR:
5531       case BlackHSideCastleFR:
5532       case BlackASideCastleFR:
5533       /* End of code added by Tord */
5534       case IllegalMove:		/* bug or odd chess variant */
5535 	if(currentMoveString[1] == '@') { // illegal drop
5536 	  *fromX = WhiteOnMove(moveNum) ?
5537 	    (int) CharToPiece(ToUpper(currentMoveString[0])) :
5538 	    (int) CharToPiece(ToLower(currentMoveString[0]));
5539 	  goto drop;
5540 	}
5541         *fromX = currentMoveString[0] - AAA;
5542         *fromY = currentMoveString[1] - ONE;
5543         *toX = currentMoveString[2] - AAA;
5544         *toY = currentMoveString[3] - ONE;
5545 	*promoChar = currentMoveString[4];
5546         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5547             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5548     if (appData.debugMode) {
5549         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5550     }
5551 	    *fromX = *fromY = *toX = *toY = 0;
5552 	    return FALSE;
5553 	}
5554 	if (appData.testLegality) {
5555 	  return (*moveType != IllegalMove);
5556 	} else {
5557 	  return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5558 			 // [HGM] lion: if this is a double move we are less critical
5559 			WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5560 	}
5561 
5562       case WhiteDrop:
5563       case BlackDrop:
5564 	*fromX = *moveType == WhiteDrop ?
5565 	  (int) CharToPiece(ToUpper(currentMoveString[0])) :
5566 	  (int) CharToPiece(ToLower(currentMoveString[0]));
5567       drop:
5568 	*fromY = DROP_RANK;
5569         *toX = currentMoveString[2] - AAA;
5570         *toY = currentMoveString[3] - ONE;
5571 	*promoChar = NULLCHAR;
5572 	return TRUE;
5573 
5574       case AmbiguousMove:
5575       case ImpossibleMove:
5576       case EndOfFile:
5577       case ElapsedTime:
5578       case Comment:
5579       case PGNTag:
5580       case NAG:
5581       case WhiteWins:
5582       case BlackWins:
5583       case GameIsDrawn:
5584       default:
5585     if (appData.debugMode) {
5586         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5587     }
5588 	/* bug? */
5589 	*fromX = *fromY = *toX = *toY = 0;
5590 	*promoChar = NULLCHAR;
5591 	return FALSE;
5592     }
5593 }
5594 
5595 Boolean pushed = FALSE;
5596 char *lastParseAttempt;
5597 
5598 void
ParsePV(char * pv,Boolean storeComments,Boolean atEnd)5599 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5600 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5601   int fromX, fromY, toX, toY; char promoChar;
5602   ChessMove moveType;
5603   Boolean valid;
5604   int nr = 0;
5605 
5606   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5607   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5608     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5609     pushed = TRUE;
5610   }
5611   endPV = forwardMostMove;
5612   do {
5613     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5614     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5615     lastParseAttempt = pv;
5616     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5617     if(!valid && nr == 0 &&
5618        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5619         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5620         // Hande case where played move is different from leading PV move
5621         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5622         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5623         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5624         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5625           endPV += 2; // if position different, keep this
5626           moveList[endPV-1][0] = fromX + AAA;
5627           moveList[endPV-1][1] = fromY + ONE;
5628           moveList[endPV-1][2] = toX + AAA;
5629           moveList[endPV-1][3] = toY + ONE;
5630           parseList[endPV-1][0] = NULLCHAR;
5631           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5632         }
5633       }
5634     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5635     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5636     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5637     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5638 	valid++; // allow comments in PV
5639 	continue;
5640     }
5641     nr++;
5642     if(endPV+1 > framePtr) break; // no space, truncate
5643     if(!valid) break;
5644     endPV++;
5645     CopyBoard(boards[endPV], boards[endPV-1]);
5646     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5647     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5648     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5649     CoordsToAlgebraic(boards[endPV - 1],
5650 			     PosFlags(endPV - 1),
5651 			     fromY, fromX, toY, toX, promoChar,
5652 			     parseList[endPV - 1]);
5653   } while(valid);
5654   if(atEnd == 2) return; // used hidden, for PV conversion
5655   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5656   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5657   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5658                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5659   DrawPosition(TRUE, boards[currentMove]);
5660 }
5661 
5662 int
MultiPV(ChessProgramState * cps)5663 MultiPV (ChessProgramState *cps)
5664 {	// check if engine supports MultiPV, and if so, return the number of the option that sets it
5665 	int i;
5666 	for(i=0; i<cps->nrOptions; i++)
5667 	    if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5668 		return i;
5669 	return -1;
5670 }
5671 
5672 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5673 
5674 Boolean
LoadMultiPV(int x,int y,char * buf,int index,int * start,int * end,int pane)5675 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5676 {
5677 	int startPV, multi, lineStart, origIndex = index;
5678 	char *p, buf2[MSG_SIZ];
5679 	ChessProgramState *cps = (pane ? &second : &first);
5680 
5681 	if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5682 	lastX = x; lastY = y;
5683 	while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5684 	lineStart = startPV = index;
5685 	while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5686 	if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5687 	index = startPV;
5688 	do{ while(buf[index] && buf[index] != '\n') index++;
5689 	} while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5690 	buf[index] = 0;
5691 	if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5692 		int n = cps->option[multi].value;
5693 		if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5694 		snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5695 		if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5696 		cps->option[multi].value = n;
5697 		*start = *end = 0;
5698 		return FALSE;
5699 	} else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5700 		ExcludeClick(origIndex - lineStart);
5701 		return FALSE;
5702 	} else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5703 		Collapse(origIndex - lineStart);
5704 		return FALSE;
5705 	}
5706 	ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5707 	*start = startPV; *end = index-1;
5708 	extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5709 	return TRUE;
5710 }
5711 
5712 char *
PvToSAN(char * pv)5713 PvToSAN (char *pv)
5714 {
5715 	static char buf[10*MSG_SIZ];
5716 	int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5717 	*buf = NULLCHAR;
5718 	if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5719 	ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5720 	for(i = forwardMostMove; i<endPV; i++){
5721 	    if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5722 	    else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5723 	    k += strlen(buf+k);
5724 	}
5725 	snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5726 	if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5727 	if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5728 	endPV = savedEnd;
5729 	return buf;
5730 }
5731 
5732 Boolean
LoadPV(int x,int y)5733 LoadPV (int x, int y)
5734 { // called on right mouse click to load PV
5735   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5736   lastX = x; lastY = y;
5737   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5738   extendGame = FALSE;
5739   return TRUE;
5740 }
5741 
5742 void
UnLoadPV()5743 UnLoadPV ()
5744 {
5745   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5746   if(endPV < 0) return;
5747   if(appData.autoCopyPV) CopyFENToClipboard();
5748   endPV = -1;
5749   if(extendGame && currentMove > forwardMostMove) {
5750 	Boolean saveAnimate = appData.animate;
5751 	if(pushed) {
5752 	    if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5753 		if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5754 	    } else storedGames--; // abandon shelved tail of original game
5755 	}
5756 	pushed = FALSE;
5757 	forwardMostMove = currentMove;
5758 	currentMove = oldFMM;
5759 	appData.animate = FALSE;
5760 	ToNrEvent(forwardMostMove);
5761 	appData.animate = saveAnimate;
5762   }
5763   currentMove = forwardMostMove;
5764   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5765   ClearPremoveHighlights();
5766   DrawPosition(TRUE, boards[currentMove]);
5767 }
5768 
5769 void
MovePV(int x,int y,int h)5770 MovePV (int x, int y, int h)
5771 { // step through PV based on mouse coordinates (called on mouse move)
5772   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5773 
5774   // we must somehow check if right button is still down (might be released off board!)
5775   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5776   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5777   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5778   if(!step) return;
5779   lastX = x; lastY = y;
5780 
5781   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5782   if(endPV < 0) return;
5783   if(y < margin) step = 1; else
5784   if(y > h - margin) step = -1;
5785   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5786   currentMove += step;
5787   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5788   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5789                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5790   DrawPosition(FALSE, boards[currentMove]);
5791 }
5792 
5793 
5794 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5795 // All positions will have equal probability, but the current method will not provide a unique
5796 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5797 #define DARK 1
5798 #define LITE 2
5799 #define ANY 3
5800 
5801 int squaresLeft[4];
5802 int piecesLeft[(int)BlackPawn];
5803 int seed, nrOfShuffles;
5804 
5805 void
GetPositionNumber()5806 GetPositionNumber ()
5807 {	// sets global variable seed
5808 	int i;
5809 
5810 	seed = appData.defaultFrcPosition;
5811 	if(seed < 0) { // randomize based on time for negative FRC position numbers
5812 		for(i=0; i<50; i++) seed += random();
5813 		seed = random() ^ random() >> 8 ^ random() << 8;
5814 		if(seed<0) seed = -seed;
5815 	}
5816 }
5817 
5818 int
put(Board board,int pieceType,int rank,int n,int shade)5819 put (Board board, int pieceType, int rank, int n, int shade)
5820 // put the piece on the (n-1)-th empty squares of the given shade
5821 {
5822 	int i;
5823 
5824 	for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5825 		if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5826 			board[rank][i] = (ChessSquare) pieceType;
5827 			squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5828 			squaresLeft[ANY]--;
5829 			piecesLeft[pieceType]--;
5830 			return i;
5831 		}
5832 	}
5833         return -1;
5834 }
5835 
5836 
5837 void
AddOnePiece(Board board,int pieceType,int rank,int shade)5838 AddOnePiece (Board board, int pieceType, int rank, int shade)
5839 // calculate where the next piece goes, (any empty square), and put it there
5840 {
5841 	int i;
5842 
5843         i = seed % squaresLeft[shade];
5844 	nrOfShuffles *= squaresLeft[shade];
5845 	seed /= squaresLeft[shade];
5846         put(board, pieceType, rank, i, shade);
5847 }
5848 
5849 void
AddTwoPieces(Board board,int pieceType,int rank)5850 AddTwoPieces (Board board, int pieceType, int rank)
5851 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5852 {
5853 	int i, n=squaresLeft[ANY], j=n-1, k;
5854 
5855 	k = n*(n-1)/2; // nr of possibilities, not counting permutations
5856         i = seed % k;  // pick one
5857 	nrOfShuffles *= k;
5858 	seed /= k;
5859 	while(i >= j) i -= j--;
5860         j = n - 1 - j; i += j;
5861         put(board, pieceType, rank, j, ANY);
5862         put(board, pieceType, rank, i, ANY);
5863 }
5864 
5865 void
SetUpShuffle(Board board,int number)5866 SetUpShuffle (Board board, int number)
5867 {
5868 	int i, p, first=1;
5869 
5870 	GetPositionNumber(); nrOfShuffles = 1;
5871 
5872 	squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5873 	squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5874 	squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5875 
5876 	for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5877 
5878 	for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5879 	    p = (int) board[0][i];
5880 	    if(p < (int) BlackPawn) piecesLeft[p] ++;
5881 	    board[0][i] = EmptySquare;
5882 	}
5883 
5884 	if(PosFlags(0) & F_ALL_CASTLE_OK) {
5885 	    // shuffles restricted to allow normal castling put KRR first
5886 	    if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5887 		put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5888 	    else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5889 		put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5890 	    if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5891 		put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5892 	    if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5893 		put(board, WhiteRook, 0, 0, ANY);
5894 	    // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5895 	}
5896 
5897 	if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5898 	    // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5899 	    for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5900 		if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5901 		while(piecesLeft[p] >= 2) {
5902 		    AddOnePiece(board, p, 0, LITE);
5903 		    AddOnePiece(board, p, 0, DARK);
5904 		}
5905 		// Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5906 	    }
5907 
5908 	for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5909 	    // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5910 	    // but we leave King and Rooks for last, to possibly obey FRC restriction
5911 	    if(p == (int)WhiteRook) continue;
5912 	    while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5913 	    if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5914 	}
5915 
5916 	// now everything is placed, except perhaps King (Unicorn) and Rooks
5917 
5918 	if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5919 	    // Last King gets castling rights
5920 	    while(piecesLeft[(int)WhiteUnicorn]) {
5921 		i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5922 		initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5923 	    }
5924 
5925 	    while(piecesLeft[(int)WhiteKing]) {
5926 		i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5927 		initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5928 	    }
5929 
5930 
5931 	} else {
5932 	    while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5933 	    while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5934 	}
5935 
5936 	// Only Rooks can be left; simply place them all
5937 	while(piecesLeft[(int)WhiteRook]) {
5938 		i = put(board, WhiteRook, 0, 0, ANY);
5939 		if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5940 			if(first) {
5941 				first=0;
5942 				initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5943 			}
5944 			initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5945 		}
5946 	}
5947 	for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5948 	    board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5949 	}
5950 
5951 	if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5952 }
5953 
5954 int
ptclen(const char * s,char * escapes)5955 ptclen (const char *s, char *escapes)
5956 {
5957     int n = 0;
5958     if(!*escapes) return strlen(s);
5959     while(*s) n += (*s != ':' && !strchr(escapes, *s)), s++;
5960     return n;
5961 }
5962 
5963 int
SetCharTableEsc(unsigned char * table,const char * map,char * escapes)5964 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5965 /* [HGM] moved here from winboard.c because of its general usefulness */
5966 /*       Basically a safe strcpy that uses the last character as King */
5967 {
5968     int result = FALSE; int NrPieces;
5969 
5970     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5971                     && NrPieces >= 12 && !(NrPieces&1)) {
5972         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5973 
5974         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5975         for( i=0; i<NrPieces/2-1; i++ ) {
5976             char *p;
5977             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5978             table[i] = map[j++];
5979             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
5980         }
5981         table[(int) WhiteKing]  = map[j++];
5982         for( i=0; i<NrPieces/2-1; i++ ) {
5983             char *p;
5984             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5985             table[WHITE_TO_BLACK i] = map[j++];
5986             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
5987         }
5988         table[(int) BlackKing]  = map[j++];
5989 
5990         result = TRUE;
5991     }
5992 
5993     return result;
5994 }
5995 
5996 int
SetCharTable(unsigned char * table,const char * map)5997 SetCharTable (unsigned char *table, const char * map)
5998 {
5999     return SetCharTableEsc(table, map, "");
6000 }
6001 
6002 void
Prelude(Board board)6003 Prelude (Board board)
6004 {	// [HGM] superchess: random selection of exo-pieces
6005 	int i, j, k; ChessSquare p;
6006 	static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6007 
6008 	GetPositionNumber(); // use FRC position number
6009 
6010 	if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6011 	    SetCharTable(pieceToChar, appData.pieceToCharTable);
6012 	    for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6013 		if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6014 	}
6015 
6016 	j = seed%4;		    seed /= 4;
6017 	p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6018 	board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6019 	board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6020 	j = seed%3 + (seed%3 >= j); seed /= 3;
6021 	p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6022 	board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6023 	board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6024 	j = seed%3;		    seed /= 3;
6025 	p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6026 	board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6027 	board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6028 	j = seed%2 + (seed%2 >= j); seed /= 2;
6029 	p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6030 	board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6031 	board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6032 	j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6033 	j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6034 	j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6035 	put(board, exoPieces[0],    0, 0, ANY);
6036 	for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6037 }
6038 
6039 void
InitPosition(int redraw)6040 InitPosition (int redraw)
6041 {
6042     ChessSquare (* pieces)[BOARD_FILES];
6043     int i, j, pawnRow=1, pieceRows=1, overrule,
6044     oldx = gameInfo.boardWidth,
6045     oldy = gameInfo.boardHeight,
6046     oldh = gameInfo.holdingsWidth;
6047     static int oldv;
6048 
6049     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6050 
6051     /* [AS] Initialize pv info list [HGM] and game status */
6052     {
6053         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6054             pvInfoList[i].depth = 0;
6055             boards[i][EP_STATUS] = EP_NONE;
6056             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6057         }
6058 
6059         initialRulePlies = 0; /* 50-move counter start */
6060 
6061         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6062         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6063     }
6064 
6065 
6066     /* [HGM] logic here is completely changed. In stead of full positions */
6067     /* the initialized data only consist of the two backranks. The switch */
6068     /* selects which one we will use, which is than copied to the Board   */
6069     /* initialPosition, which for the rest is initialized by Pawns and    */
6070     /* empty squares. This initial position is then copied to boards[0],  */
6071     /* possibly after shuffling, so that it remains available.            */
6072 
6073     gameInfo.holdingsWidth = 0; /* default board sizes */
6074     gameInfo.boardWidth    = 8;
6075     gameInfo.boardHeight   = 8;
6076     gameInfo.holdingsSize  = 0;
6077     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6078     for(i=0; i<BOARD_FILES-6; i++)
6079       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6080     initialPosition[EP_STATUS] = EP_NONE;
6081     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6082     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6083     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6084          SetCharTable(pieceNickName, appData.pieceNickNames);
6085     else SetCharTable(pieceNickName, "............");
6086     pieces = FIDEArray;
6087 
6088     switch (gameInfo.variant) {
6089     case VariantFischeRandom:
6090       shuffleOpenings = TRUE;
6091       appData.fischerCastling = TRUE;
6092     default:
6093       break;
6094     case VariantShatranj:
6095       pieces = ShatranjArray;
6096       nrCastlingRights = 0;
6097       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6098       break;
6099     case VariantMakruk:
6100       pieces = makrukArray;
6101       nrCastlingRights = 0;
6102       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6103       break;
6104     case VariantASEAN:
6105       pieces = aseanArray;
6106       nrCastlingRights = 0;
6107       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6108       break;
6109     case VariantTwoKings:
6110       pieces = twoKingsArray;
6111       break;
6112     case VariantGrand:
6113       pieces = GrandArray;
6114       nrCastlingRights = 0;
6115       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6116       gameInfo.boardWidth = 10;
6117       gameInfo.boardHeight = 10;
6118       gameInfo.holdingsSize = 7;
6119       break;
6120     case VariantCapaRandom:
6121       shuffleOpenings = TRUE;
6122       appData.fischerCastling = TRUE;
6123     case VariantCapablanca:
6124       pieces = CapablancaArray;
6125       gameInfo.boardWidth = 10;
6126       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6127       break;
6128     case VariantGothic:
6129       pieces = GothicArray;
6130       gameInfo.boardWidth = 10;
6131       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6132       break;
6133     case VariantSChess:
6134       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6135       gameInfo.holdingsSize = 7;
6136       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6137       break;
6138     case VariantJanus:
6139       pieces = JanusArray;
6140       gameInfo.boardWidth = 10;
6141       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6142       nrCastlingRights = 6;
6143         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6144         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6145         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6146         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6147         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6148         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6149       break;
6150     case VariantFalcon:
6151       pieces = FalconArray;
6152       gameInfo.boardWidth = 10;
6153       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6154       break;
6155     case VariantXiangqi:
6156       pieces = XiangqiArray;
6157       gameInfo.boardWidth  = 9;
6158       gameInfo.boardHeight = 10;
6159       nrCastlingRights = 0;
6160       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6161       break;
6162     case VariantShogi:
6163       pieces = ShogiArray;
6164       gameInfo.boardWidth  = 9;
6165       gameInfo.boardHeight = 9;
6166       gameInfo.holdingsSize = 7;
6167       nrCastlingRights = 0;
6168       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6169       break;
6170     case VariantChu:
6171       pieces = ChuArray; pieceRows = 3;
6172       gameInfo.boardWidth  = 12;
6173       gameInfo.boardHeight = 12;
6174       nrCastlingRights = 0;
6175       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN:+.++.++++++++++.+++++K"
6176                                    "p.brqsexogcathd.vmlifn:+.++.++++++++++.+++++k", SUFFIXES);
6177       break;
6178     case VariantCourier:
6179       pieces = CourierArray;
6180       gameInfo.boardWidth  = 12;
6181       nrCastlingRights = 0;
6182       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6183       break;
6184     case VariantKnightmate:
6185       pieces = KnightmateArray;
6186       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6187       break;
6188     case VariantSpartan:
6189       pieces = SpartanArray;
6190       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6191       break;
6192     case VariantLion:
6193       pieces = lionArray;
6194       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6195       break;
6196     case VariantChuChess:
6197       pieces = ChuChessArray;
6198       gameInfo.boardWidth = 10;
6199       gameInfo.boardHeight = 10;
6200       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6201       break;
6202     case VariantFairy:
6203       pieces = fairyArray;
6204       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6205       break;
6206     case VariantGreat:
6207       pieces = GreatArray;
6208       gameInfo.boardWidth = 10;
6209       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6210       gameInfo.holdingsSize = 8;
6211       break;
6212     case VariantSuper:
6213       pieces = FIDEArray;
6214       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6215       gameInfo.holdingsSize = 8;
6216       startedFromSetupPosition = TRUE;
6217       break;
6218     case VariantCrazyhouse:
6219     case VariantBughouse:
6220       pieces = FIDEArray;
6221       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6222       gameInfo.holdingsSize = 5;
6223       break;
6224     case VariantWildCastle:
6225       pieces = FIDEArray;
6226       /* !!?shuffle with kings guaranteed to be on d or e file */
6227       shuffleOpenings = 1;
6228       break;
6229     case VariantNoCastle:
6230       pieces = FIDEArray;
6231       nrCastlingRights = 0;
6232       /* !!?unconstrained back-rank shuffle */
6233       shuffleOpenings = 1;
6234       break;
6235     }
6236 
6237     overrule = 0;
6238     if(appData.NrFiles >= 0) {
6239         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6240         gameInfo.boardWidth = appData.NrFiles;
6241     }
6242     if(appData.NrRanks >= 0) {
6243         gameInfo.boardHeight = appData.NrRanks;
6244     }
6245     if(appData.holdingsSize >= 0) {
6246         i = appData.holdingsSize;
6247         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6248         gameInfo.holdingsSize = i;
6249     }
6250     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6251     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6252         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6253 
6254     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6255     if(pawnRow < 1) pawnRow = 1;
6256     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6257        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6258     if(gameInfo.variant == VariantChu) pawnRow = 3;
6259 
6260     /* User pieceToChar list overrules defaults */
6261     if(appData.pieceToCharTable != NULL)
6262         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6263 
6264     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6265 
6266         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6267             s = (ChessSquare) 0; /* account holding counts in guard band */
6268         for( i=0; i<BOARD_HEIGHT; i++ )
6269             initialPosition[i][j] = s;
6270 
6271         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6272         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6273         initialPosition[pawnRow][j] = WhitePawn;
6274         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6275         if(gameInfo.variant == VariantXiangqi) {
6276             if(j&1) {
6277                 initialPosition[pawnRow][j] =
6278                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6279                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6280                    initialPosition[2][j] = WhiteCannon;
6281                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6282                 }
6283             }
6284         }
6285         if(gameInfo.variant == VariantChu) {
6286              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6287                initialPosition[pawnRow+1][j] = WhiteCobra,
6288                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6289              for(i=1; i<pieceRows; i++) {
6290                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6291                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6292              }
6293         }
6294         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6295             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6296                initialPosition[0][j] = WhiteRook;
6297                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6298             }
6299         }
6300         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6301     }
6302     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6303     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6304 
6305             j=BOARD_LEFT+1;
6306             initialPosition[1][j] = WhiteBishop;
6307             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6308             j=BOARD_RGHT-2;
6309             initialPosition[1][j] = WhiteRook;
6310             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6311     }
6312 
6313     if( nrCastlingRights == -1) {
6314         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6315         /*       This sets default castling rights from none to normal corners   */
6316         /* Variants with other castling rights must set them themselves above    */
6317         nrCastlingRights = 6;
6318 
6319         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6320         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6321         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6322         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6323         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6324         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6325      }
6326 
6327      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6328      if(gameInfo.variant == VariantGreat) { // promotion commoners
6329 	initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6330 	initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6331 	initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6332 	initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6333      }
6334      if( gameInfo.variant == VariantSChess ) {
6335       initialPosition[1][0] = BlackMarshall;
6336       initialPosition[2][0] = BlackAngel;
6337       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6338       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6339       initialPosition[1][1] = initialPosition[2][1] =
6340       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6341      }
6342   if (appData.debugMode) {
6343     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6344   }
6345     if(shuffleOpenings) {
6346 	SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6347 	startedFromSetupPosition = TRUE;
6348     }
6349     if(startedFromPositionFile) {
6350       /* [HGM] loadPos: use PositionFile for every new game */
6351       CopyBoard(initialPosition, filePosition);
6352       for(i=0; i<nrCastlingRights; i++)
6353           initialRights[i] = filePosition[CASTLING][i];
6354       startedFromSetupPosition = TRUE;
6355     }
6356 
6357     CopyBoard(boards[0], initialPosition);
6358 
6359     if(oldx != gameInfo.boardWidth ||
6360        oldy != gameInfo.boardHeight ||
6361        oldv != gameInfo.variant ||
6362        oldh != gameInfo.holdingsWidth
6363                                          )
6364             InitDrawingSizes(-2 ,0);
6365 
6366     oldv = gameInfo.variant;
6367     if (redraw)
6368       DrawPosition(TRUE, boards[currentMove]);
6369 }
6370 
6371 void
SendBoard(ChessProgramState * cps,int moveNum)6372 SendBoard (ChessProgramState *cps, int moveNum)
6373 {
6374     char message[MSG_SIZ];
6375 
6376     if (cps->useSetboard) {
6377       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6378       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6379       SendToProgram(message, cps);
6380       free(fen);
6381 
6382     } else {
6383       ChessSquare *bp;
6384       int i, j, left=0, right=BOARD_WIDTH;
6385       /* Kludge to set black to move, avoiding the troublesome and now
6386        * deprecated "black" command.
6387        */
6388       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6389         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6390 
6391       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6392 
6393       SendToProgram("edit\n", cps);
6394       SendToProgram("#\n", cps);
6395       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6396 	bp = &boards[moveNum][i][left];
6397         for (j = left; j < right; j++, bp++) {
6398 	  if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6399 	  if ((int) *bp < (int) BlackPawn) {
6400 	    if(j == BOARD_RGHT+1)
6401 		 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6402 	    else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6403             if(message[0] == '+' || message[0] == '~') {
6404 	      snprintf(message, MSG_SIZ,"%c%c%c+\n",
6405                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6406                         AAA + j, ONE + i);
6407             }
6408             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6409                 message[1] = BOARD_RGHT   - 1 - j + '1';
6410                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6411             }
6412 	    SendToProgram(message, cps);
6413 	  }
6414 	}
6415       }
6416 
6417       SendToProgram("c\n", cps);
6418       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6419 	bp = &boards[moveNum][i][left];
6420         for (j = left; j < right; j++, bp++) {
6421 	  if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6422 	  if (((int) *bp != (int) EmptySquare)
6423 	      && ((int) *bp >= (int) BlackPawn)) {
6424 	    if(j == BOARD_LEFT-2)
6425 		 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6426 	    else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6427                     AAA + j, ONE + i);
6428             if(message[0] == '+' || message[0] == '~') {
6429 	      snprintf(message, MSG_SIZ,"%c%c%c+\n",
6430                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6431                         AAA + j, ONE + i);
6432             }
6433             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6434                 message[1] = BOARD_RGHT   - 1 - j + '1';
6435                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6436             }
6437 	    SendToProgram(message, cps);
6438 	  }
6439 	}
6440       }
6441 
6442       SendToProgram(".\n", cps);
6443     }
6444     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6445 }
6446 
6447 char exclusionHeader[MSG_SIZ];
6448 int exCnt, excludePtr;
6449 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6450 static Exclusion excluTab[200];
6451 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6452 
6453 static void
WriteMap(int s)6454 WriteMap (int s)
6455 {
6456     int j;
6457     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6458     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6459 }
6460 
6461 static void
ClearMap()6462 ClearMap ()
6463 {
6464     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6465     excludePtr = 24; exCnt = 0;
6466     WriteMap(0);
6467 }
6468 
6469 static void
UpdateExcludeHeader(int fromY,int fromX,int toY,int toX,char promoChar,char state)6470 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6471 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6472     char buf[2*MOVE_LEN], *p;
6473     Exclusion *e = excluTab;
6474     int i;
6475     for(i=0; i<exCnt; i++)
6476 	if(e[i].ff == fromX && e[i].fr == fromY &&
6477 	   e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6478     if(i == exCnt) { // was not in exclude list; add it
6479 	CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6480 	if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6481 	    if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6482 	    return; // abort
6483 	}
6484 	e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6485 	excludePtr++; e[i].mark = excludePtr++;
6486 	for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6487 	exCnt++;
6488     }
6489     exclusionHeader[e[i].mark] = state;
6490 }
6491 
6492 static int
ExcludeOneMove(int fromY,int fromX,int toY,int toX,char promoChar,char state)6493 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6494 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6495     char buf[MSG_SIZ];
6496     int j, k;
6497     ChessMove moveType;
6498     if((signed char)promoChar == -1) { // kludge to indicate best move
6499 	if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6500 	    return 1; // if unparsable, abort
6501     }
6502     // update exclusion map (resolving toggle by consulting existing state)
6503     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6504     j = k%8; k >>= 3;
6505     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6506     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6507 	 excludeMap[k] |=   1<<j;
6508     else excludeMap[k] &= ~(1<<j);
6509     // update header
6510     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6511     // inform engine
6512     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6513     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6514     SendToBoth(buf);
6515     return (state == '+');
6516 }
6517 
6518 static void
ExcludeClick(int index)6519 ExcludeClick (int index)
6520 {
6521     int i, j;
6522     Exclusion *e = excluTab;
6523     if(index < 25) { // none, best or tail clicked
6524 	if(index < 13) { // none: include all
6525 	    WriteMap(0); // clear map
6526 	    for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6527 	    SendToBoth("include all\n"); // and inform engine
6528 	} else if(index > 18) { // tail
6529 	    if(exclusionHeader[19] == '-') { // tail was excluded
6530 		SendToBoth("include all\n");
6531 		WriteMap(0); // clear map completely
6532 		// now re-exclude selected moves
6533 		for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6534 		    ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6535 	    } else { // tail was included or in mixed state
6536 		SendToBoth("exclude all\n");
6537 		WriteMap(0xFF); // fill map completely
6538 		// now re-include selected moves
6539 		j = 0; // count them
6540 		for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6541 		    ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6542 		if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6543 	    }
6544 	} else { // best
6545 	    ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6546 	}
6547     } else {
6548 	for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6549 	    char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6550 	    ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6551 	    break;
6552 	}
6553     }
6554 }
6555 
6556 ChessSquare
DefaultPromoChoice(int white)6557 DefaultPromoChoice (int white)
6558 {
6559     ChessSquare result;
6560     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6561        gameInfo.variant == VariantMakruk)
6562 	result = WhiteFerz; // no choice
6563     else if(gameInfo.variant == VariantASEAN)
6564 	result = WhiteRook; // no choice
6565     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6566 	result= WhiteKing; // in Suicide Q is the last thing we want
6567     else if(gameInfo.variant == VariantSpartan)
6568 	result = white ? WhiteQueen : WhiteAngel;
6569     else result = WhiteQueen;
6570     if(!white) result = WHITE_TO_BLACK result;
6571     return result;
6572 }
6573 
6574 static int autoQueen; // [HGM] oneclick
6575 
6576 int
HasPromotionChoice(int fromX,int fromY,int toX,int toY,char * promoChoice,int sweepSelect)6577 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6578 {
6579     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6580     /* [HGM] add Shogi promotions */
6581     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6582     ChessSquare piece, partner;
6583     ChessMove moveType;
6584     Boolean premove;
6585 
6586     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6587     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6588 
6589     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6590       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6591 	return FALSE;
6592 
6593     piece = boards[currentMove][fromY][fromX];
6594     if(gameInfo.variant == VariantChu) {
6595         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6596         promotionZoneSize = BOARD_HEIGHT/3;
6597         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6598     } else if(gameInfo.variant == VariantShogi) {
6599         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6600         highestPromotingPiece = (int)WhiteAlfil;
6601     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6602         promotionZoneSize = 3;
6603     }
6604 
6605     // Treat Lance as Pawn when it is not representing Amazon or Lance
6606     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6607         if(piece == WhiteLance) piece = WhitePawn; else
6608         if(piece == BlackLance) piece = BlackPawn;
6609     }
6610 
6611     // next weed out all moves that do not touch the promotion zone at all
6612     if((int)piece >= BlackPawn) {
6613         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6614              return FALSE;
6615         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6616         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6617     } else {
6618         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6619            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6620         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6621              return FALSE;
6622     }
6623 
6624     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6625 
6626     // weed out mandatory Shogi promotions
6627     if(gameInfo.variant == VariantShogi) {
6628 	if(piece >= BlackPawn) {
6629 	    if(toY == 0 && piece == BlackPawn ||
6630 	       toY == 0 && piece == BlackQueen ||
6631 	       toY <= 1 && piece == BlackKnight) {
6632 		*promoChoice = '+';
6633 		return FALSE;
6634 	    }
6635 	} else {
6636 	    if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6637 	       toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6638 	       toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6639 		*promoChoice = '+';
6640 		return FALSE;
6641 	    }
6642 	}
6643     }
6644 
6645     // weed out obviously illegal Pawn moves
6646     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6647 	if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6648 	if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6649 	if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6650 	if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6651 	// note we are not allowed to test for valid (non-)capture, due to premove
6652     }
6653 
6654     // we either have a choice what to promote to, or (in Shogi) whether to promote
6655     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6656        gameInfo.variant == VariantMakruk) {
6657 	ChessSquare p=BlackFerz;  // no choice
6658 	while(p < EmptySquare) {  //but make sure we use piece that exists
6659 	    *promoChoice = PieceToChar(p++);
6660 	    if(*promoChoice != '.') break;
6661 	}
6662 	return FALSE;
6663     }
6664     // no sense asking what we must promote to if it is going to explode...
6665     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6666 	*promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6667 	return FALSE;
6668     }
6669     // give caller the default choice even if we will not make it
6670     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6671     partner = piece; // pieces can promote if the pieceToCharTable says so
6672     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6673     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6674     if(        sweepSelect && gameInfo.variant != VariantGreat
6675 			   && gameInfo.variant != VariantGrand
6676 			   && gameInfo.variant != VariantSuper) return FALSE;
6677     if(autoQueen) return FALSE; // predetermined
6678 
6679     // suppress promotion popup on illegal moves that are not premoves
6680     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6681 	      gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6682     if(appData.testLegality && !premove) {
6683 	moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6684 			fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6685         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6686 	if(moveType != WhitePromotion && moveType  != BlackPromotion)
6687 	    return FALSE;
6688     }
6689 
6690     return TRUE;
6691 }
6692 
6693 int
InPalace(int row,int column)6694 InPalace (int row, int column)
6695 {   /* [HGM] for Xiangqi */
6696     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6697          column < (BOARD_WIDTH + 4)/2 &&
6698          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6699     return FALSE;
6700 }
6701 
6702 int
PieceForSquare(int x,int y)6703 PieceForSquare (int x, int y)
6704 {
6705   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6706      return -1;
6707   else
6708      return boards[currentMove][y][x];
6709 }
6710 
6711 int
OKToStartUserMove(int x,int y)6712 OKToStartUserMove (int x, int y)
6713 {
6714     ChessSquare from_piece;
6715     int white_piece;
6716 
6717     if (matchMode) return FALSE;
6718     if (gameMode == EditPosition) return TRUE;
6719 
6720     if (x >= 0 && y >= 0)
6721       from_piece = boards[currentMove][y][x];
6722     else
6723       from_piece = EmptySquare;
6724 
6725     if (from_piece == EmptySquare) return FALSE;
6726 
6727     white_piece = (int)from_piece >= (int)WhitePawn &&
6728       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6729 
6730     switch (gameMode) {
6731       case AnalyzeFile:
6732       case TwoMachinesPlay:
6733       case EndOfGame:
6734 	return FALSE;
6735 
6736       case IcsObserving:
6737       case IcsIdle:
6738 	return FALSE;
6739 
6740       case MachinePlaysWhite:
6741       case IcsPlayingBlack:
6742 	if (appData.zippyPlay) return FALSE;
6743 	if (white_piece) {
6744 	    DisplayMoveError(_("You are playing Black"));
6745 	    return FALSE;
6746 	}
6747 	break;
6748 
6749       case MachinePlaysBlack:
6750       case IcsPlayingWhite:
6751 	if (appData.zippyPlay) return FALSE;
6752 	if (!white_piece) {
6753 	    DisplayMoveError(_("You are playing White"));
6754 	    return FALSE;
6755 	}
6756 	break;
6757 
6758       case PlayFromGameFile:
6759 	    if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6760       case EditGame:
6761 	if (!white_piece && WhiteOnMove(currentMove)) {
6762 	    DisplayMoveError(_("It is White's turn"));
6763 	    return FALSE;
6764 	}
6765 	if (white_piece && !WhiteOnMove(currentMove)) {
6766 	    DisplayMoveError(_("It is Black's turn"));
6767 	    return FALSE;
6768 	}
6769 	if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6770 	    /* Editing correspondence game history */
6771 	    /* Could disallow this or prompt for confirmation */
6772 	    cmailOldMove = -1;
6773 	}
6774 	break;
6775 
6776       case BeginningOfGame:
6777 	if (appData.icsActive) return FALSE;
6778 	if (!appData.noChessProgram) {
6779 	    if (!white_piece) {
6780 		DisplayMoveError(_("You are playing White"));
6781 		return FALSE;
6782 	    }
6783 	}
6784 	break;
6785 
6786       case Training:
6787 	if (!white_piece && WhiteOnMove(currentMove)) {
6788 	    DisplayMoveError(_("It is White's turn"));
6789 	    return FALSE;
6790 	}
6791 	if (white_piece && !WhiteOnMove(currentMove)) {
6792 	    DisplayMoveError(_("It is Black's turn"));
6793 	    return FALSE;
6794 	}
6795 	break;
6796 
6797       default:
6798       case IcsExamining:
6799 	break;
6800     }
6801     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6802 	&& gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6803 	&& gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6804 	&& gameMode != AnalyzeFile && gameMode != Training) {
6805 	DisplayMoveError(_("Displayed position is not current"));
6806 	return FALSE;
6807     }
6808     return TRUE;
6809 }
6810 
6811 Boolean
OnlyMove(int * x,int * y,Boolean captures)6812 OnlyMove (int *x, int *y, Boolean captures)
6813 {
6814     DisambiguateClosure cl;
6815     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6816     switch(gameMode) {
6817       case MachinePlaysBlack:
6818       case IcsPlayingWhite:
6819       case BeginningOfGame:
6820 	if(!WhiteOnMove(currentMove)) return FALSE;
6821 	break;
6822       case MachinePlaysWhite:
6823       case IcsPlayingBlack:
6824 	if(WhiteOnMove(currentMove)) return FALSE;
6825 	break;
6826       case EditGame:
6827         break;
6828       default:
6829 	return FALSE;
6830     }
6831     cl.pieceIn = EmptySquare;
6832     cl.rfIn = *y;
6833     cl.ffIn = *x;
6834     cl.rtIn = -1;
6835     cl.ftIn = -1;
6836     cl.promoCharIn = NULLCHAR;
6837     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6838     if( cl.kind == NormalMove ||
6839 	cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6840 	cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6841 	cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6842       fromX = cl.ff;
6843       fromY = cl.rf;
6844       *x = cl.ft;
6845       *y = cl.rt;
6846       return TRUE;
6847     }
6848     if(cl.kind != ImpossibleMove) return FALSE;
6849     cl.pieceIn = EmptySquare;
6850     cl.rfIn = -1;
6851     cl.ffIn = -1;
6852     cl.rtIn = *y;
6853     cl.ftIn = *x;
6854     cl.promoCharIn = NULLCHAR;
6855     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6856     if( cl.kind == NormalMove ||
6857 	cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6858 	cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6859 	cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6860       fromX = cl.ff;
6861       fromY = cl.rf;
6862       *x = cl.ft;
6863       *y = cl.rt;
6864       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6865       return TRUE;
6866     }
6867     return FALSE;
6868 }
6869 
6870 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6871 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6872 int lastLoadGameUseList = FALSE;
6873 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6874 ChessMove lastLoadGameStart = EndOfFile;
6875 int doubleClick;
6876 Boolean addToBookFlag;
6877 
6878 void
UserMoveEvent(int fromX,int fromY,int toX,int toY,int promoChar)6879 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6880 {
6881     ChessMove moveType;
6882     ChessSquare pup;
6883     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6884 
6885     /* Check if the user is playing in turn.  This is complicated because we
6886        let the user "pick up" a piece before it is his turn.  So the piece he
6887        tried to pick up may have been captured by the time he puts it down!
6888        Therefore we use the color the user is supposed to be playing in this
6889        test, not the color of the piece that is currently on the starting
6890        square---except in EditGame mode, where the user is playing both
6891        sides; fortunately there the capture race can't happen.  (It can
6892        now happen in IcsExamining mode, but that's just too bad.  The user
6893        will get a somewhat confusing message in that case.)
6894        */
6895 
6896     switch (gameMode) {
6897       case AnalyzeFile:
6898       case TwoMachinesPlay:
6899       case EndOfGame:
6900       case IcsObserving:
6901       case IcsIdle:
6902 	/* We switched into a game mode where moves are not accepted,
6903            perhaps while the mouse button was down. */
6904         return;
6905 
6906       case MachinePlaysWhite:
6907 	/* User is moving for Black */
6908 	if (WhiteOnMove(currentMove)) {
6909 	    DisplayMoveError(_("It is White's turn"));
6910             return;
6911 	}
6912 	break;
6913 
6914       case MachinePlaysBlack:
6915 	/* User is moving for White */
6916 	if (!WhiteOnMove(currentMove)) {
6917 	    DisplayMoveError(_("It is Black's turn"));
6918             return;
6919 	}
6920 	break;
6921 
6922       case PlayFromGameFile:
6923 	    if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6924       case EditGame:
6925       case IcsExamining:
6926       case BeginningOfGame:
6927       case AnalyzeMode:
6928       case Training:
6929 	if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6930 	if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6931             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6932 	    /* User is moving for Black */
6933 	    if (WhiteOnMove(currentMove)) {
6934 		DisplayMoveError(_("It is White's turn"));
6935                 return;
6936 	    }
6937 	} else {
6938 	    /* User is moving for White */
6939 	    if (!WhiteOnMove(currentMove)) {
6940 		DisplayMoveError(_("It is Black's turn"));
6941                 return;
6942 	    }
6943 	}
6944 	break;
6945 
6946       case IcsPlayingBlack:
6947 	/* User is moving for Black */
6948 	if (WhiteOnMove(currentMove)) {
6949 	    if (!appData.premove) {
6950 		DisplayMoveError(_("It is White's turn"));
6951 	    } else if (toX >= 0 && toY >= 0) {
6952 		premoveToX = toX;
6953 		premoveToY = toY;
6954 		premoveFromX = fromX;
6955 		premoveFromY = fromY;
6956 		premovePromoChar = promoChar;
6957 		gotPremove = 1;
6958 		if (appData.debugMode)
6959 		    fprintf(debugFP, "Got premove: fromX %d,"
6960 			    "fromY %d, toX %d, toY %d\n",
6961 			    fromX, fromY, toX, toY);
6962 	    }
6963             return;
6964 	}
6965 	break;
6966 
6967       case IcsPlayingWhite:
6968 	/* User is moving for White */
6969 	if (!WhiteOnMove(currentMove)) {
6970 	    if (!appData.premove) {
6971 		DisplayMoveError(_("It is Black's turn"));
6972 	    } else if (toX >= 0 && toY >= 0) {
6973 		premoveToX = toX;
6974 		premoveToY = toY;
6975 		premoveFromX = fromX;
6976 		premoveFromY = fromY;
6977 		premovePromoChar = promoChar;
6978 		gotPremove = 1;
6979 		if (appData.debugMode)
6980 		    fprintf(debugFP, "Got premove: fromX %d,"
6981 			    "fromY %d, toX %d, toY %d\n",
6982 			    fromX, fromY, toX, toY);
6983 	    }
6984             return;
6985 	}
6986 	break;
6987 
6988       default:
6989 	break;
6990 
6991       case EditPosition:
6992 	/* EditPosition, empty square, or different color piece;
6993 	   click-click move is possible */
6994 	if (toX == -2 || toY == -2) {
6995 	    boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6996 	    DrawPosition(FALSE, boards[currentMove]);
6997 	    return;
6998 	} else if (toX >= 0 && toY >= 0) {
6999 	    if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7000 		ChessSquare q, p = boards[0][rf][ff];
7001 		if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7002 		if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
7003 		else p = CHUDEMOTED (q = boards[0][rf][ff]);
7004 		if(PieceToChar(q) == '+') gatingPiece = p;
7005 	    }
7006 	    boards[0][toY][toX] = boards[0][fromY][fromX];
7007 	    if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7008 		if(boards[0][fromY][0] != EmptySquare) {
7009 		    if(boards[0][fromY][1]) boards[0][fromY][1]--;
7010 		    if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7011 		}
7012 	    } else
7013 	    if(fromX == BOARD_RGHT+1) {
7014 		if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7015 		    if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7016 		    if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7017 		}
7018 	    } else
7019 	    boards[0][fromY][fromX] = gatingPiece;
7020 	    DrawPosition(FALSE, boards[currentMove]);
7021 	    return;
7022 	}
7023         return;
7024     }
7025 
7026     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7027     pup = boards[currentMove][toY][toX];
7028 
7029     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7030     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7031          if( pup != EmptySquare ) return;
7032          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7033 	   if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7034 		moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7035 	   // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7036 	   if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7037 	   fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7038 	   while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7039          fromY = DROP_RANK;
7040     }
7041 
7042     /* [HGM] always test for legality, to get promotion info */
7043     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7044                                          fromY, fromX, toY, toX, promoChar);
7045 
7046     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7047 
7048     /* [HGM] but possibly ignore an IllegalMove result */
7049     if (appData.testLegality) {
7050 	if (moveType == IllegalMove || moveType == ImpossibleMove) {
7051 	    DisplayMoveError(_("Illegal move"));
7052             return;
7053 	}
7054     }
7055 
7056     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7057         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7058 	     ClearPremoveHighlights(); // was included
7059 	else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7060 	return;
7061     }
7062 
7063     if(addToBookFlag) { // adding moves to book
7064 	char buf[MSG_SIZ], move[MSG_SIZ];
7065         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7066 	if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7067 	snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7068 	AddBookMove(buf);
7069 	addToBookFlag = FALSE;
7070 	ClearHighlights();
7071 	return;
7072     }
7073 
7074     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7075 }
7076 
7077 /* Common tail of UserMoveEvent and DropMenuEvent */
7078 int
FinishMove(ChessMove moveType,int fromX,int fromY,int toX,int toY,int promoChar)7079 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7080 {
7081     char *bookHit = 0;
7082 
7083     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7084 	// [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7085 	int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7086 	if(WhiteOnMove(currentMove)) {
7087 	    if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7088 	} else {
7089 	    if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7090 	}
7091     }
7092 
7093     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7094        move type in caller when we know the move is a legal promotion */
7095     if(moveType == NormalMove && promoChar)
7096         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7097 
7098     /* [HGM] <popupFix> The following if has been moved here from
7099        UserMoveEvent(). Because it seemed to belong here (why not allow
7100        piece drops in training games?), and because it can only be
7101        performed after it is known to what we promote. */
7102     if (gameMode == Training) {
7103       /* compare the move played on the board to the next move in the
7104        * game. If they match, display the move and the opponent's response.
7105        * If they don't match, display an error message.
7106        */
7107       int saveAnimate;
7108       Board testBoard;
7109       CopyBoard(testBoard, boards[currentMove]);
7110       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7111 
7112       if (CompareBoards(testBoard, boards[currentMove+1])) {
7113 	ForwardInner(currentMove+1);
7114 
7115 	/* Autoplay the opponent's response.
7116 	 * if appData.animate was TRUE when Training mode was entered,
7117 	 * the response will be animated.
7118 	 */
7119 	saveAnimate = appData.animate;
7120 	appData.animate = animateTraining;
7121 	ForwardInner(currentMove+1);
7122 	appData.animate = saveAnimate;
7123 
7124 	/* check for the end of the game */
7125 	if (currentMove >= forwardMostMove) {
7126 	  gameMode = PlayFromGameFile;
7127 	  ModeHighlight();
7128 	  SetTrainingModeOff();
7129 	  DisplayInformation(_("End of game"));
7130 	}
7131       } else {
7132 	DisplayError(_("Incorrect move"), 0);
7133       }
7134       return 1;
7135     }
7136 
7137   /* Ok, now we know that the move is good, so we can kill
7138      the previous line in Analysis Mode */
7139   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7140 				&& currentMove < forwardMostMove) {
7141     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7142     else forwardMostMove = currentMove;
7143   }
7144 
7145   ClearMap();
7146 
7147   /* If we need the chess program but it's dead, restart it */
7148   ResurrectChessProgram();
7149 
7150   /* A user move restarts a paused game*/
7151   if (pausing)
7152     PauseEvent();
7153 
7154   thinkOutput[0] = NULLCHAR;
7155 
7156   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7157 
7158   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7159     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7160     return 1;
7161   }
7162 
7163   if (gameMode == BeginningOfGame) {
7164     if (appData.noChessProgram) {
7165       gameMode = EditGame;
7166       SetGameInfo();
7167     } else {
7168       char buf[MSG_SIZ];
7169       gameMode = MachinePlaysBlack;
7170       StartClocks();
7171       SetGameInfo();
7172       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7173       DisplayTitle(buf);
7174       if (first.sendName) {
7175 	snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7176 	SendToProgram(buf, &first);
7177       }
7178       StartClocks();
7179     }
7180     ModeHighlight();
7181   }
7182 
7183   /* Relay move to ICS or chess engine */
7184   if (appData.icsActive) {
7185     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7186 	gameMode == IcsExamining) {
7187       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7188         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7189 	SendToICS("draw ");
7190         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7191       }
7192       // also send plain move, in case ICS does not understand atomic claims
7193       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7194       ics_user_moved = 1;
7195     }
7196   } else {
7197     if (first.sendTime && (gameMode == BeginningOfGame ||
7198 			   gameMode == MachinePlaysWhite ||
7199 			   gameMode == MachinePlaysBlack)) {
7200       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7201     }
7202     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7203 	 // [HGM] book: if program might be playing, let it use book
7204 	bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7205 	first.maybeThinking = TRUE;
7206     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7207 	if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7208 	SendBoard(&first, currentMove+1);
7209 	if(second.analyzing) {
7210 	    if(!second.useSetboard) SendToProgram("undo\n", &second);
7211 	    SendBoard(&second, currentMove+1);
7212 	}
7213     } else {
7214 	SendMoveToProgram(forwardMostMove-1, &first);
7215 	if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7216     }
7217     if (currentMove == cmailOldMove + 1) {
7218       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7219     }
7220   }
7221 
7222   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7223 
7224   switch (gameMode) {
7225   case EditGame:
7226     if(appData.testLegality)
7227     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7228     case MT_NONE:
7229     case MT_CHECK:
7230       break;
7231     case MT_CHECKMATE:
7232     case MT_STAINMATE:
7233       if (WhiteOnMove(currentMove)) {
7234 	GameEnds(BlackWins, "Black mates", GE_PLAYER);
7235       } else {
7236 	GameEnds(WhiteWins, "White mates", GE_PLAYER);
7237       }
7238       break;
7239     case MT_STALEMATE:
7240       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7241       break;
7242     }
7243     break;
7244 
7245   case MachinePlaysBlack:
7246   case MachinePlaysWhite:
7247     /* disable certain menu options while machine is thinking */
7248     SetMachineThinkingEnables();
7249     break;
7250 
7251   default:
7252     break;
7253   }
7254 
7255   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7256   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7257 
7258   if(bookHit) { // [HGM] book: simulate book reply
7259 	static char bookMove[MSG_SIZ]; // a bit generous?
7260 
7261 	programStats.nodes = programStats.depth = programStats.time =
7262 	programStats.score = programStats.got_only_move = 0;
7263 	sprintf(programStats.movelist, "%s (xbook)", bookHit);
7264 
7265 	safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7266 	strcat(bookMove, bookHit);
7267 	HandleMachineMove(bookMove, &first);
7268   }
7269   return 1;
7270 }
7271 
7272 void
MarkByFEN(char * fen)7273 MarkByFEN(char *fen)
7274 {
7275 	int r, f;
7276 	if(!appData.markers || !appData.highlightDragging) return;
7277 	for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7278 	r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7279 	while(*fen) {
7280 	    int s = 0;
7281 	    marker[r][f] = 0;
7282 	    if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7283 	    if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7284 	    if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7285 	    if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7286 	    if(*fen == 'T') marker[r][f++] = 0; else
7287 	    if(*fen == 'Y') marker[r][f++] = 1; else
7288 	    if(*fen == 'G') marker[r][f++] = 3; else
7289 	    if(*fen == 'B') marker[r][f++] = 4; else
7290 	    if(*fen == 'C') marker[r][f++] = 5; else
7291 	    if(*fen == 'M') marker[r][f++] = 6; else
7292 	    if(*fen == 'W') marker[r][f++] = 7; else
7293 	    if(*fen == 'D') marker[r][f++] = 8; else
7294 	    if(*fen == 'R') marker[r][f++] = 2; else {
7295 		while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7296 	      f += s; fen -= s>0;
7297 	    }
7298 	    while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7299 	    if(r < 0) break;
7300 	    fen++;
7301 	}
7302 	DrawPosition(TRUE, NULL);
7303 }
7304 
7305 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7306 
7307 void
Mark(Board board,int flags,ChessMove kind,int rf,int ff,int rt,int ft,VOIDSTAR closure)7308 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7309 {
7310     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7311     Markers *m = (Markers *) closure;
7312     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7313 	(*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7314 			 || kind == WhiteCapturesEnPassant
7315 			 || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7316     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7317 }
7318 
7319 static int hoverSavedValid;
7320 
7321 void
MarkTargetSquares(int clear)7322 MarkTargetSquares (int clear)
7323 {
7324   int x, y, sum=0;
7325   if(clear) { // no reason to ever suppress clearing
7326     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7327     hoverSavedValid = 0;
7328     if(!sum) return; // nothing was cleared,no redraw needed
7329   } else {
7330     int capt = 0;
7331     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7332        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7333     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7334     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7335       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7336       if(capt)
7337       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7338     }
7339   }
7340   DrawPosition(FALSE, NULL);
7341 }
7342 
7343 int
Explode(Board board,int fromX,int fromY,int toX,int toY)7344 Explode (Board board, int fromX, int fromY, int toX, int toY)
7345 {
7346     if(gameInfo.variant == VariantAtomic &&
7347        (board[toY][toX] != EmptySquare ||                     // capture?
7348         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7349                          board[fromY][fromX] == BlackPawn   )
7350       )) {
7351         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7352         return TRUE;
7353     }
7354     return FALSE;
7355 }
7356 
7357 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7358 
7359 int
CanPromote(ChessSquare piece,int y)7360 CanPromote (ChessSquare piece, int y)
7361 {
7362         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7363 	if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7364 	// some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7365 	if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7366 	   gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7367 	   gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7368          gameInfo.variant == VariantMakruk) return FALSE;
7369 	return (piece == BlackPawn && y <= zone ||
7370 		piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7371 		piece == BlackLance && y <= zone ||
7372 		piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7373 }
7374 
7375 void
HoverEvent(int xPix,int yPix,int x,int y)7376 HoverEvent (int xPix, int yPix, int x, int y)
7377 {
7378 	static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7379 	int r, f;
7380 	if(!first.highlight) return;
7381 	if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7382 	if(x == oldX && y == oldY) return; // only do something if we enter new square
7383 	oldFromX = fromX; oldFromY = fromY;
7384 	if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7385 	  for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7386 	    baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7387 	  hoverSavedValid = 1;
7388 	} else if(oldX != x || oldY != y) {
7389 	  // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7390 	  if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7391 	  for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7392 	    marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7393 	  if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7394  	    char buf[MSG_SIZ];
7395 	    snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7396 	    SendToProgram(buf, &first);
7397 	  }
7398 	  oldX = x; oldY = y;
7399 //	  SetHighlights(fromX, fromY, x, y);
7400 	}
7401 }
7402 
ReportClick(char * action,int x,int y)7403 void ReportClick(char *action, int x, int y)
7404 {
7405 	char buf[MSG_SIZ]; // Inform engine of what user does
7406 	int r, f;
7407 	if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7408 	  for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7409 	    legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7410 	if(!first.highlight || gameMode == EditPosition) return;
7411 	snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7412 	SendToProgram(buf, &first);
7413 }
7414 
7415 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7416 
7417 void
LeftClick(ClickType clickType,int xPix,int yPix)7418 LeftClick (ClickType clickType, int xPix, int yPix)
7419 {
7420     int x, y;
7421     Boolean saveAnimate;
7422     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7423     char promoChoice = NULLCHAR;
7424     ChessSquare piece;
7425     static TimeMark lastClickTime, prevClickTime;
7426 
7427     x = EventToSquare(xPix, BOARD_WIDTH);
7428     y = EventToSquare(yPix, BOARD_HEIGHT);
7429     if (!flipView && y >= 0) {
7430 	y = BOARD_HEIGHT - 1 - y;
7431     }
7432     if (flipView && x >= 0) {
7433 	x = BOARD_WIDTH - 1 - x;
7434     }
7435 
7436     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7437 	static int dummy;
7438 	RightClick(clickType, xPix, yPix, &dummy, &dummy);
7439 	right = TRUE;
7440 	return;
7441     }
7442 
7443     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7444 
7445     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7446 
7447     if (clickType == Press) ErrorPopDown();
7448     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7449 
7450     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7451 	defaultPromoChoice = promoSweep;
7452 	promoSweep = EmptySquare;   // terminate sweep
7453 	promoDefaultAltered = TRUE;
7454 	if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7455     }
7456 
7457     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7458 	if(clickType == Release) return; // ignore upclick of click-click destination
7459 	promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7460 	if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7461 	if(gameInfo.holdingsWidth &&
7462 		(WhiteOnMove(currentMove)
7463 			? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7464 			: x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7465 	    // click in right holdings, for determining promotion piece
7466 	    ChessSquare p = boards[currentMove][y][x];
7467 	    if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7468 	    if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7469 	    if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7470 		FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7471 		fromX = fromY = -1;
7472 		return;
7473 	    }
7474 	}
7475 	DrawPosition(FALSE, boards[currentMove]);
7476 	return;
7477     }
7478 
7479     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7480     if(clickType == Press
7481             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7482               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7483               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7484 	return;
7485 
7486     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7487 	// could be static click on premove from-square: abort premove
7488 	gotPremove = 0;
7489 	ClearPremoveHighlights();
7490     }
7491 
7492     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7493 	fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7494 
7495     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7496 	int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7497 		    gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7498 	defaultPromoChoice = DefaultPromoChoice(side);
7499     }
7500 
7501     autoQueen = appData.alwaysPromoteToQueen;
7502 
7503     if (fromX == -1) {
7504       int originalY = y;
7505       gatingPiece = EmptySquare;
7506       if (clickType != Press) {
7507 	if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7508 	    DragPieceEnd(xPix, yPix); dragging = 0;
7509 	    DrawPosition(FALSE, NULL);
7510 	}
7511 	return;
7512       }
7513       doubleClick = FALSE;
7514       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7515 	doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7516       }
7517       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7518       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7519 	 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7520 	 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7521 	    /* First square */
7522 	    if (OKToStartUserMove(fromX, fromY)) {
7523 		second = 0;
7524 		ReportClick("lift", x, y);
7525 		MarkTargetSquares(0);
7526 		if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7527 		DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7528 		if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7529 		    promoSweep = defaultPromoChoice;
7530 		    selectFlag = 0; lastX = xPix; lastY = yPix;
7531 		    Sweep(0); // Pawn that is going to promote: preview promotion piece
7532 		    DisplayMessage("", _("Pull pawn backwards to under-promote"));
7533 		}
7534 		if (appData.highlightDragging) {
7535 		    SetHighlights(fromX, fromY, -1, -1);
7536 		} else {
7537 		    ClearHighlights();
7538 		}
7539 	    } else fromX = fromY = -1;
7540 	    return;
7541 	}
7542     }
7543 printf("to click %d,%d\n",x,y);
7544     /* fromX != -1 */
7545     if (clickType == Press && gameMode != EditPosition) {
7546 	ChessSquare fromP;
7547 	ChessSquare toP;
7548 	int frc;
7549 
7550 	// ignore off-board to clicks
7551 	if(y < 0 || x < 0) return;
7552 
7553 	/* Check if clicking again on the same color piece */
7554 	fromP = boards[currentMove][fromY][fromX];
7555 	toP = boards[currentMove][y][x];
7556 	frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7557  	if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7558 	    marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7559 	   ((WhitePawn <= fromP && fromP <= WhiteKing &&
7560 	     WhitePawn <= toP && toP <= WhiteKing &&
7561 	     !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7562 	     !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7563 	    (BlackPawn <= fromP && fromP <= BlackKing &&
7564 	     BlackPawn <= toP && toP <= BlackKing &&
7565 	     !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7566 	     !(fromP == BlackKing && toP == BlackRook && frc)))) {
7567 	    /* Clicked again on same color piece -- changed his mind */
7568 	    second = (x == fromX && y == fromY);
7569 	    killX = killY = -1;
7570 	    if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7571 		second = FALSE; // first double-click rather than scond click
7572 		doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7573 	    }
7574 	    promoDefaultAltered = FALSE;
7575 	    MarkTargetSquares(1);
7576 	   if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7577 	    if (appData.highlightDragging) {
7578 		SetHighlights(x, y, -1, -1);
7579 	    } else {
7580 		ClearHighlights();
7581 	    }
7582 	    if (OKToStartUserMove(x, y)) {
7583 		if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7584 		  (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7585                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7586                  gatingPiece = boards[currentMove][fromY][fromX];
7587 		else gatingPiece = doubleClick ? fromP : EmptySquare;
7588 		fromX = x;
7589 		fromY = y; dragging = 1;
7590 		if(!second) ReportClick("lift", x, y);
7591 		MarkTargetSquares(0);
7592 		DragPieceBegin(xPix, yPix, FALSE);
7593 		if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7594 		    promoSweep = defaultPromoChoice;
7595 		    selectFlag = 0; lastX = xPix; lastY = yPix;
7596 		    Sweep(0); // Pawn that is going to promote: preview promotion piece
7597 		}
7598 	    }
7599 	   }
7600 	   if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7601 	   second = FALSE;
7602 	}
7603 	// ignore clicks on holdings
7604 	if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7605     }
7606 printf("A type=%d\n",clickType);
7607 
7608     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7609 	gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7610 	return;
7611     }
7612 
7613     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7614 	DragPieceEnd(xPix, yPix); dragging = 0;
7615 	if(clearFlag) {
7616 	    // a deferred attempt to click-click move an empty square on top of a piece
7617 	    boards[currentMove][y][x] = EmptySquare;
7618 	    ClearHighlights();
7619 	    DrawPosition(FALSE, boards[currentMove]);
7620 	    fromX = fromY = -1; clearFlag = 0;
7621 	    return;
7622 	}
7623 	if (appData.animateDragging) {
7624 	    /* Undo animation damage if any */
7625 	    DrawPosition(FALSE, NULL);
7626 	}
7627 	if (second) {
7628 	    /* Second up/down in same square; just abort move */
7629 	    second = 0;
7630 	    fromX = fromY = -1;
7631 	    gatingPiece = EmptySquare;
7632 	    MarkTargetSquares(1);
7633 	    ClearHighlights();
7634 	    gotPremove = 0;
7635 	    ClearPremoveHighlights();
7636 	} else {
7637 	    /* First upclick in same square; start click-click mode */
7638 	    SetHighlights(x, y, -1, -1);
7639 	}
7640 	return;
7641     }
7642 
7643     clearFlag = 0;
7644 printf("B\n");
7645     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7646        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7647 	if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7648 	DisplayMessage(_("only marked squares are legal"),"");
7649 	DrawPosition(TRUE, NULL);
7650 	return; // ignore to-click
7651     }
7652 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7653     /* we now have a different from- and (possibly off-board) to-square */
7654     /* Completed move */
7655     if(!sweepSelecting) {
7656 	toX = x;
7657 	toY = y;
7658     }
7659 
7660     piece = boards[currentMove][fromY][fromX];
7661 
7662     saveAnimate = appData.animate;
7663     if (clickType == Press) {
7664 	if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7665 	if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7666 	    // must be Edit Position mode with empty-square selected
7667 	    fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7668 	    if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7669 	    return;
7670 	}
7671 	if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7672 	    return;
7673 	}
7674 	if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7675 	    killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7676 	} else
7677 	if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7678 	if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7679 	  if(appData.sweepSelect) {
7680 	    promoSweep = defaultPromoChoice;
7681 	    if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7682 	    selectFlag = 0; lastX = xPix; lastY = yPix;
7683 	    Sweep(0); // Pawn that is going to promote: preview promotion piece
7684 	    sweepSelecting = 1;
7685 	    DisplayMessage("", _("Pull pawn backwards to under-promote"));
7686 	    MarkTargetSquares(1);
7687 	  }
7688 	  return; // promo popup appears on up-click
7689 	}
7690 	/* Finish clickclick move */
7691 	if (appData.animate || appData.highlightLastMove) {
7692 	    SetHighlights(fromX, fromY, toX, toY);
7693 	} else {
7694 	    ClearHighlights();
7695 	}
7696     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7697 	sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7698 	if (appData.animate || appData.highlightLastMove) {
7699 	    SetHighlights(fromX, fromY, toX, toY);
7700 	} else {
7701 	    ClearHighlights();
7702 	}
7703     } else {
7704 #if 0
7705 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7706 	/* Finish drag move */
7707 	if (appData.highlightLastMove) {
7708 	    SetHighlights(fromX, fromY, toX, toY);
7709 	} else {
7710 	    ClearHighlights();
7711 	}
7712 #endif
7713 	if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7714 	if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7715 	  dragging *= 2;            // flag button-less dragging if we are dragging
7716 	  MarkTargetSquares(1);
7717 	  if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7718 	  else {
7719 	    kill2X = killX; kill2Y = killY;
7720 	    killX = x; killY = y;     //remeber this square as intermediate
7721 	    ReportClick("put", x, y); // and inform engine
7722 	    ReportClick("lift", x, y);
7723 	    MarkTargetSquares(0);
7724 	    return;
7725 	  }
7726 	}
7727 	DragPieceEnd(xPix, yPix); dragging = 0;
7728 	/* Don't animate move and drag both */
7729 	appData.animate = FALSE;
7730     }
7731 
7732     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7733     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7734 	ChessSquare piece = boards[currentMove][fromY][fromX];
7735 	if(gameMode == EditPosition && piece != EmptySquare &&
7736 	   fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7737 	    int n;
7738 
7739 	    if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7740 		n = PieceToNumber(piece - (int)BlackPawn);
7741 		if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7742 		boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7743 		boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7744 	    } else
7745 	    if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7746 		n = PieceToNumber(piece);
7747 		if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7748 		boards[currentMove][n][BOARD_WIDTH-1] = piece;
7749 		boards[currentMove][n][BOARD_WIDTH-2]++;
7750 	    }
7751 	    boards[currentMove][fromY][fromX] = EmptySquare;
7752 	}
7753 	ClearHighlights();
7754 	fromX = fromY = -1;
7755         MarkTargetSquares(1);
7756 	DrawPosition(TRUE, boards[currentMove]);
7757 	return;
7758     }
7759 
7760     // off-board moves should not be highlighted
7761     if(x < 0 || y < 0) ClearHighlights();
7762     else ReportClick("put", x, y);
7763 
7764     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7765 
7766     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7767 
7768     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7769 	SetHighlights(fromX, fromY, toX, toY);
7770         MarkTargetSquares(1);
7771 	if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7772 	    // [HGM] super: promotion to captured piece selected from holdings
7773 	    ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7774 	    promotionChoice = TRUE;
7775 	    // kludge follows to temporarily execute move on display, without promoting yet
7776 	    boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7777 	    boards[currentMove][toY][toX] = p;
7778 	    DrawPosition(FALSE, boards[currentMove]);
7779 	    boards[currentMove][fromY][fromX] = p; // take back, but display stays
7780 	    boards[currentMove][toY][toX] = q;
7781 	    DisplayMessage("Click in holdings to choose piece", "");
7782 	    return;
7783 	}
7784 	PromotionPopUp(promoChoice);
7785     } else {
7786 	int oldMove = currentMove;
7787 	UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7788 	if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7789 	if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7790 	if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7791 	   Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7792 	    DrawPosition(TRUE, boards[currentMove]);
7793         MarkTargetSquares(1);
7794 	fromX = fromY = -1;
7795     }
7796     appData.animate = saveAnimate;
7797     if (appData.animate || appData.animateDragging) {
7798 	/* Undo animation damage if needed */
7799 	DrawPosition(FALSE, NULL);
7800     }
7801 }
7802 
7803 int
RightClick(ClickType action,int x,int y,int * fromX,int * fromY)7804 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7805 {   // front-end-free part taken out of PieceMenuPopup
7806     int whichMenu; int xSqr, ySqr;
7807 
7808     if(seekGraphUp) { // [HGM] seekgraph
7809 	if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7810 	if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7811 	return -2;
7812     }
7813 
7814     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7815 	 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7816 	if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7817 	if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7818 	if(action == Press)   {
7819 	    originalFlip = flipView;
7820 	    flipView = !flipView; // temporarily flip board to see game from partners perspective
7821 	    DrawPosition(TRUE, partnerBoard);
7822 	    DisplayMessage(partnerStatus, "");
7823 	    partnerUp = TRUE;
7824 	} else if(action == Release) {
7825 	    flipView = originalFlip;
7826 	    DrawPosition(TRUE, boards[currentMove]);
7827 	    partnerUp = FALSE;
7828 	}
7829 	return -2;
7830     }
7831 
7832     xSqr = EventToSquare(x, BOARD_WIDTH);
7833     ySqr = EventToSquare(y, BOARD_HEIGHT);
7834     if (action == Release) {
7835 	if(pieceSweep != EmptySquare) {
7836 	    EditPositionMenuEvent(pieceSweep, toX, toY);
7837 	    pieceSweep = EmptySquare;
7838 	} else UnLoadPV(); // [HGM] pv
7839     }
7840     if (action != Press) return -2; // return code to be ignored
7841     switch (gameMode) {
7842       case IcsExamining:
7843 	if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7844       case EditPosition:
7845 	if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7846 	if (xSqr < 0 || ySqr < 0) return -1;
7847 	if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7848 	pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7849 	toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7850 	if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7851 	NextPiece(0);
7852 	return 2; // grab
7853       case IcsObserving:
7854 	if(!appData.icsEngineAnalyze) return -1;
7855       case IcsPlayingWhite:
7856       case IcsPlayingBlack:
7857 	if(!appData.zippyPlay) goto noZip;
7858       case AnalyzeMode:
7859       case AnalyzeFile:
7860       case MachinePlaysWhite:
7861       case MachinePlaysBlack:
7862       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7863 	if (!appData.dropMenu) {
7864 	  LoadPV(x, y);
7865 	  return 2; // flag front-end to grab mouse events
7866 	}
7867 	if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7868            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7869       case EditGame:
7870       noZip:
7871 	if (xSqr < 0 || ySqr < 0) return -1;
7872 	if (!appData.dropMenu || appData.testLegality &&
7873 	    gameInfo.variant != VariantBughouse &&
7874 	    gameInfo.variant != VariantCrazyhouse) return -1;
7875 	whichMenu = 1; // drop menu
7876 	break;
7877       default:
7878 	return -1;
7879     }
7880 
7881     if (((*fromX = xSqr) < 0) ||
7882 	((*fromY = ySqr) < 0)) {
7883 	*fromX = *fromY = -1;
7884 	return -1;
7885     }
7886     if (flipView)
7887       *fromX = BOARD_WIDTH - 1 - *fromX;
7888     else
7889       *fromY = BOARD_HEIGHT - 1 - *fromY;
7890 
7891     return whichMenu;
7892 }
7893 
7894 void
SendProgramStatsToFrontend(ChessProgramState * cps,ChessProgramStats * cpstats)7895 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7896 {
7897 //    char * hint = lastHint;
7898     FrontEndProgramStats stats;
7899 
7900     stats.which = cps == &first ? 0 : 1;
7901     stats.depth = cpstats->depth;
7902     stats.nodes = cpstats->nodes;
7903     stats.score = cpstats->score;
7904     stats.time = cpstats->time;
7905     stats.pv = cpstats->movelist;
7906     stats.hint = lastHint;
7907     stats.an_move_index = 0;
7908     stats.an_move_count = 0;
7909 
7910     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7911         stats.hint = cpstats->move_name;
7912         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7913         stats.an_move_count = cpstats->nr_moves;
7914     }
7915 
7916     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
7917 
7918     SetProgramStats( &stats );
7919 }
7920 
7921 void
ClearEngineOutputPane(int which)7922 ClearEngineOutputPane (int which)
7923 {
7924     static FrontEndProgramStats dummyStats;
7925     dummyStats.which = which;
7926     dummyStats.pv = "#";
7927     SetProgramStats( &dummyStats );
7928 }
7929 
7930 #define MAXPLAYERS 500
7931 
7932 char *
TourneyStandings(int display)7933 TourneyStandings (int display)
7934 {
7935     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7936     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7937     char result, *p, *names[MAXPLAYERS];
7938 
7939     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7940 	return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7941     names[0] = p = strdup(appData.participants);
7942     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7943 
7944     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7945 
7946     while(result = appData.results[nr]) {
7947 	color = Pairing(nr, nPlayers, &w, &b, &dummy);
7948 	if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7949 	wScore = bScore = 0;
7950 	switch(result) {
7951 	  case '+': wScore = 2; break;
7952 	  case '-': bScore = 2; break;
7953 	  case '=': wScore = bScore = 1; break;
7954 	  case ' ':
7955 	  case '*': return strdup("busy"); // tourney not finished
7956 	}
7957 	score[w] += wScore;
7958 	score[b] += bScore;
7959 	games[w]++;
7960 	games[b]++;
7961 	nr++;
7962     }
7963     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7964     for(w=0; w<nPlayers; w++) {
7965 	bScore = -1;
7966 	for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7967 	ranking[w] = b; points[w] = bScore; score[b] = -2;
7968     }
7969     p = malloc(nPlayers*34+1);
7970     for(w=0; w<nPlayers && w<display; w++)
7971 	sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7972     free(names[0]);
7973     return p;
7974 }
7975 
7976 void
Count(Board board,int pCnt[],int * nW,int * nB,int * wStale,int * bStale,int * bishopColor)7977 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7978 {	// count all piece types
7979 	int p, f, r;
7980 	*nB = *nW = *wStale = *bStale = *bishopColor = 0;
7981 	for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7982 	for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7983 		p = board[r][f];
7984 		pCnt[p]++;
7985 		if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7986 		if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7987 		if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7988 		if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7989 		   p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7990 			*bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7991 	}
7992 }
7993 
7994 int
SufficientDefence(int pCnt[],int side,int nMine,int nHis)7995 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7996 {
7997 	int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7998 	int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7999 
8000 	nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8001 	if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8002 	if(myPawns == 2 && nMine == 3) // KPP
8003 	    return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8004 	if(myPawns == 1 && nMine == 2) // KP
8005 	    return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8006 	if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8007 	    return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8008 	if(myPawns) return FALSE;
8009 	if(pCnt[WhiteRook+side])
8010 	    return pCnt[BlackRook-side] ||
8011 		   pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8012 		   pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8013 		   pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8014 	if(pCnt[WhiteCannon+side]) {
8015 	    if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8016 	    return majorDefense || pCnt[BlackAlfil-side] >= 2;
8017 	}
8018 	if(pCnt[WhiteKnight+side])
8019 	    return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8020 	return FALSE;
8021 }
8022 
8023 int
MatingPotential(int pCnt[],int side,int nMine,int nHis,int stale,int bisColor)8024 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8025 {
8026 	VariantClass v = gameInfo.variant;
8027 
8028 	if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8029 	if(v == VariantShatranj) return TRUE; // always winnable through baring
8030 	if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8031 	if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8032 
8033 	if(v == VariantXiangqi) {
8034 		int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8035 
8036 		nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8037 		if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8038 		if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8039 		if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8040 		// if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8041 		if(stale) // we have at least one last-rank P plus perhaps C
8042 		    return majors // KPKX
8043 		        || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8044 		else // KCA*E*
8045 		    return pCnt[WhiteFerz+side] // KCAK
8046 		        || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8047 		        || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8048 		// TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8049 
8050 	} else if(v == VariantKnightmate) {
8051 		if(nMine == 1) return FALSE;
8052 		if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8053 	} else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8054 		int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8055 
8056 		if(nMine == 1) return FALSE; // bare King
8057 		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
8058 		nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8059 		if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8060 		// by now we have King + 1 piece (or multiple Bishops on the same color)
8061 		if(pCnt[WhiteKnight+side])
8062 			return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8063 				pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8064 			     || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8065 		if(nBishops)
8066 			return (pCnt[BlackKnight-side]); // KBKN, KFKN
8067 		if(pCnt[WhiteAlfil+side])
8068 			return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8069 		if(pCnt[WhiteWazir+side])
8070 			return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8071 	}
8072 
8073 	return TRUE;
8074 }
8075 
8076 int
CompareWithRights(Board b1,Board b2)8077 CompareWithRights (Board b1, Board b2)
8078 {
8079     int rights = 0;
8080     if(!CompareBoards(b1, b2)) return FALSE;
8081     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8082     /* compare castling rights */
8083     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8084            rights++; /* King lost rights, while rook still had them */
8085     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8086         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8087            rights++; /* but at least one rook lost them */
8088     }
8089     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8090            rights++;
8091     if( b1[CASTLING][5] != NoRights ) {
8092         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8093            rights++;
8094     }
8095     return rights == 0;
8096 }
8097 
8098 int
Adjudicate(ChessProgramState * cps)8099 Adjudicate (ChessProgramState *cps)
8100 {	// [HGM] some adjudications useful with buggy engines
8101 	// [HGM] adjudicate: made into separate routine, which now can be called after every move
8102 	//       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8103 	//       Actually ending the game is now based on the additional internal condition canAdjudicate.
8104 	//       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8105 	int k, drop, count = 0; static int bare = 1;
8106 	ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8107 	Boolean canAdjudicate = !appData.icsActive;
8108 
8109 	// most tests only when we understand the game, i.e. legality-checking on
8110 	    if( appData.testLegality )
8111 	    {   /* [HGM] Some more adjudications for obstinate engines */
8112 		int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8113 		static int moveCount = 6;
8114 		ChessMove result;
8115 		char *reason = NULL;
8116 
8117                 /* Count what is on board. */
8118 		Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8119 
8120 		/* Some material-based adjudications that have to be made before stalemate test */
8121 		if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8122 		    // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8123 		     boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8124 		     if(canAdjudicate && appData.checkMates) {
8125 			 if(engineOpponent)
8126 			   SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8127                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8128 							"Xboard adjudication: King destroyed", GE_XBOARD );
8129                          return 1;
8130 		     }
8131 		}
8132 
8133 		/* Bare King in Shatranj (loses) or Losers (wins) */
8134                 if( nrW == 1 || nrB == 1) {
8135                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8136 		     boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8137 		     if(canAdjudicate && appData.checkMates) {
8138 			 if(engineOpponent)
8139 			   SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8140                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8141 							"Xboard adjudication: Bare king", GE_XBOARD );
8142                          return 1;
8143 		     }
8144 		  } else
8145                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8146                   {    /* bare King */
8147 			boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8148 			if(canAdjudicate && appData.checkMates) {
8149 			    /* but only adjudicate if adjudication enabled */
8150 			    if(engineOpponent)
8151 			      SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8152 			    GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8153 							"Xboard adjudication: Bare king", GE_XBOARD );
8154 			    return 1;
8155 			}
8156 		  }
8157                 } else bare = 1;
8158 
8159 
8160             // don't wait for engine to announce game end if we can judge ourselves
8161             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8162 	      case MT_CHECK:
8163 		if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8164 		    int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8165 		    for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8166 			if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8167 			    checkCnt++;
8168 			if(checkCnt >= 2) {
8169 			    reason = "Xboard adjudication: 3rd check";
8170 			    boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8171 			    break;
8172 			}
8173 		    }
8174 		}
8175 	      case MT_NONE:
8176 	      default:
8177 		break;
8178 	      case MT_STEALMATE:
8179 	      case MT_STALEMATE:
8180 	      case MT_STAINMATE:
8181 		reason = "Xboard adjudication: Stalemate";
8182 		if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8183 		    boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8184 		    if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8185 			boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8186 		    else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8187 			boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8188 						   ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8189 									EP_CHECKMATE : EP_WINS);
8190 		    else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8191 		        boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8192 		}
8193 		break;
8194 	      case MT_CHECKMATE:
8195 		reason = "Xboard adjudication: Checkmate";
8196 		boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8197 		if(gameInfo.variant == VariantShogi) {
8198 		    if(forwardMostMove > backwardMostMove
8199 		       && moveList[forwardMostMove-1][1] == '@'
8200 		       && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8201 			reason = "XBoard adjudication: pawn-drop mate";
8202 			boards[forwardMostMove][EP_STATUS] = EP_WINS;
8203 		    }
8204 		}
8205 		break;
8206 	    }
8207 
8208 		switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8209 		    case EP_STALEMATE:
8210 			result = GameIsDrawn; break;
8211 		    case EP_CHECKMATE:
8212 			result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8213 		    case EP_WINS:
8214 			result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8215 		    default:
8216 			result = EndOfFile;
8217 		}
8218                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8219 		    if(engineOpponent)
8220 		      SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8221 		    GameEnds( result, reason, GE_XBOARD );
8222 		    return 1;
8223 		}
8224 
8225                 /* Next absolutely insufficient mating material. */
8226                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8227                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8228                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8229 
8230                      /* always flag draws, for judging claims */
8231                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8232 
8233                      if(canAdjudicate && appData.materialDraws) {
8234                          /* but only adjudicate them if adjudication enabled */
8235 			 if(engineOpponent) {
8236 			   SendToProgram("force\n", engineOpponent); // suppress reply
8237 			   SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8238 			 }
8239                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8240                          return 1;
8241                      }
8242                 }
8243 
8244                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8245                 if(gameInfo.variant == VariantXiangqi ?
8246                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8247                  : nrW + nrB == 4 &&
8248                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8249                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8250                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8251                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8252                    ) ) {
8253                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8254                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8255 			  if(engineOpponent) {
8256 			    SendToProgram("force\n", engineOpponent); // suppress reply
8257 			    SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8258 			  }
8259                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8260                           return 1;
8261                      }
8262                 } else moveCount = 6;
8263 	    }
8264 
8265 	// Repetition draws and 50-move rule can be applied independently of legality testing
8266 
8267                 /* Check for rep-draws */
8268                 count = 0;
8269                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8270                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8271                 for(k = forwardMostMove-2;
8272                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8273                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8274                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8275                     k-=2)
8276                 {   int rights=0;
8277                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8278                         /* compare castling rights */
8279                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8280                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8281                                 rights++; /* King lost rights, while rook still had them */
8282                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8283                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8284                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8285                                    rights++; /* but at least one rook lost them */
8286                         }
8287                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8288                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8289                                 rights++;
8290                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8291                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8292                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8293                                    rights++;
8294                         }
8295                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8296                             && appData.drawRepeats > 1) {
8297                              /* adjudicate after user-specified nr of repeats */
8298 			     int result = GameIsDrawn;
8299 			     char *details = "XBoard adjudication: repetition draw";
8300 			     if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8301 				// [HGM] xiangqi: check for forbidden perpetuals
8302 				int m, ourPerpetual = 1, hisPerpetual = 1;
8303 				for(m=forwardMostMove; m>k; m-=2) {
8304 				    if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8305 					ourPerpetual = 0; // the current mover did not always check
8306 				    if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8307 					hisPerpetual = 0; // the opponent did not always check
8308 				}
8309 				if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8310 									ourPerpetual, hisPerpetual);
8311 				if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8312 				    result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8313 		 		    details = "Xboard adjudication: perpetual checking";
8314 				} else
8315 				if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8316 				    break; // (or we would have caught him before). Abort repetition-checking loop.
8317 				} else
8318 				if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8319 				    if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8320 					result = BlackWins;
8321 					details = "Xboard adjudication: repetition";
8322 				    }
8323 				} else // it must be XQ
8324 				// Now check for perpetual chases
8325 				if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8326 				    hisPerpetual = PerpetualChase(k, forwardMostMove);
8327 				    ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8328 				    if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8329 					static char resdet[MSG_SIZ];
8330 					result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8331 		 			details = resdet;
8332 					snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8333 				    } else
8334 				    if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8335 					break; // Abort repetition-checking loop.
8336 				}
8337 				// if neither of us is checking or chasing all the time, or both are, it is draw
8338 			     }
8339 			     if(engineOpponent) {
8340 			       SendToProgram("force\n", engineOpponent); // suppress reply
8341 			       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8342 			     }
8343                              GameEnds( result, details, GE_XBOARD );
8344                              return 1;
8345                         }
8346                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8347                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8348                     }
8349                 }
8350 
8351                 /* Now we test for 50-move draws. Determine ply count */
8352                 count = forwardMostMove;
8353                 /* look for last irreversble move */
8354                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8355                     count--;
8356                 /* if we hit starting position, add initial plies */
8357                 if( count == backwardMostMove )
8358                     count -= initialRulePlies;
8359                 count = forwardMostMove - count;
8360 		if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8361 			// adjust reversible move counter for checks in Xiangqi
8362 			int i = forwardMostMove - count, inCheck = 0, lastCheck;
8363 			if(i < backwardMostMove) i = backwardMostMove;
8364 			while(i <= forwardMostMove) {
8365 				lastCheck = inCheck; // check evasion does not count
8366 				inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8367 				if(inCheck || lastCheck) count--; // check does not count
8368 				i++;
8369 			}
8370 		}
8371                 if( count >= 100)
8372                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8373                          /* this is used to judge if draw claims are legal */
8374                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8375 			 if(engineOpponent) {
8376 			   SendToProgram("force\n", engineOpponent); // suppress reply
8377 			   SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8378 			 }
8379                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8380                          return 1;
8381                 }
8382 
8383                 /* if draw offer is pending, treat it as a draw claim
8384                  * when draw condition present, to allow engines a way to
8385                  * claim draws before making their move to avoid a race
8386                  * condition occurring after their move
8387                  */
8388 		if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8389                          char *p = NULL;
8390                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8391                              p = "Draw claim: 50-move rule";
8392                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8393                              p = "Draw claim: 3-fold repetition";
8394                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8395                              p = "Draw claim: insufficient mating material";
8396                          if( p != NULL && canAdjudicate) {
8397 			     if(engineOpponent) {
8398 			       SendToProgram("force\n", engineOpponent); // suppress reply
8399 			       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8400 			     }
8401                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8402                              return 1;
8403                          }
8404                 }
8405 
8406 	        if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8407 		    if(engineOpponent) {
8408 		      SendToProgram("force\n", engineOpponent); // suppress reply
8409 		      SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8410 		    }
8411 	            GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8412 	            return 1;
8413         	}
8414 	return 0;
8415 }
8416 
8417 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8418 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8419 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8420 
8421 static int
BitbaseProbe()8422 BitbaseProbe ()
8423 {
8424     int pieces[10], squares[10], cnt=0, r, f, res;
8425     static int loaded;
8426     static PPROBE_EGBB probeBB;
8427     if(!appData.testLegality) return 10;
8428     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8429     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8430     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8431     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8432 	ChessSquare piece = boards[forwardMostMove][r][f];
8433 	int black = (piece >= BlackPawn);
8434 	int type = piece - black*BlackPawn;
8435 	if(piece == EmptySquare) continue;
8436 	if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8437 	if(type == WhiteKing) type = WhiteQueen + 1;
8438 	type = egbbCode[type];
8439 	squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8440         pieces[cnt] = type + black*6;
8441 	if(++cnt > 5) return 11;
8442     }
8443     pieces[cnt] = squares[cnt] = 0;
8444     // probe EGBB
8445     if(loaded == 2) return 13; // loading failed before
8446     if(loaded == 0) {
8447 	char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8448 	HMODULE lib;
8449 	PLOAD_EGBB loadBB;
8450 	loaded = 2; // prepare for failure
8451 	if(!path) return 13; // no egbb installed
8452 	strncpy(buf, path + 8, MSG_SIZ);
8453 	if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8454 	snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8455 	lib = LoadLibrary(buf);
8456 	if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8457 	loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8458 	probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8459 	if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8460 	p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8461 	loaded = 1; // success!
8462     }
8463     res = probeBB(forwardMostMove & 1, pieces, squares);
8464     return res > 0 ? 1 : res < 0 ? -1 : 0;
8465 }
8466 
8467 char *
SendMoveToBookUser(int moveNr,ChessProgramState * cps,int initial)8468 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8469 {   // [HGM] book: this routine intercepts moves to simulate book replies
8470     char *bookHit = NULL;
8471 
8472     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8473 	char buf[MSG_SIZ];
8474 	snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8475 	SendToProgram(buf, cps);
8476     }
8477     //first determine if the incoming move brings opponent into his book
8478     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8479 	bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8480     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8481     if(bookHit != NULL && !cps->bookSuspend) {
8482 	// make sure opponent is not going to reply after receiving move to book position
8483 	SendToProgram("force\n", cps);
8484 	cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8485     }
8486     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8487     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8488     // now arrange restart after book miss
8489     if(bookHit) {
8490 	// after a book hit we never send 'go', and the code after the call to this routine
8491 	// has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8492 	char buf[MSG_SIZ], *move = bookHit;
8493 	if(cps->useSAN) {
8494 	    int fromX, fromY, toX, toY;
8495 	    char promoChar;
8496 	    ChessMove moveType;
8497 	    move = buf + 30;
8498 	    if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8499 				 &fromX, &fromY, &toX, &toY, &promoChar)) {
8500 		(void) CoordsToAlgebraic(boards[forwardMostMove],
8501 				    PosFlags(forwardMostMove),
8502 				    fromY, fromX, toY, toX, promoChar, move);
8503 	    } else {
8504 		if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8505 		bookHit = NULL;
8506 	    }
8507 	}
8508 	snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8509 	SendToProgram(buf, cps);
8510 	if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8511     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8512 	SendToProgram("go\n", cps);
8513 	cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8514     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8515 	if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8516 	    SendToProgram("go\n", cps);
8517 	cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8518     }
8519     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8520 }
8521 
8522 int
LoadError(char * errmess,ChessProgramState * cps)8523 LoadError (char *errmess, ChessProgramState *cps)
8524 {   // unloads engine and switches back to -ncp mode if it was first
8525     if(cps->initDone) return FALSE;
8526     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8527     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8528     cps->pr = NoProc;
8529     if(cps == &first) {
8530 	appData.noChessProgram = TRUE;
8531 	gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8532 	gameMode = BeginningOfGame; ModeHighlight();
8533 	SetNCPMode();
8534     }
8535     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8536     DisplayMessage("", ""); // erase waiting message
8537     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8538     return TRUE;
8539 }
8540 
8541 char *savedMessage;
8542 ChessProgramState *savedState;
8543 void
DeferredBookMove(void)8544 DeferredBookMove (void)
8545 {
8546 	if(savedState->lastPing != savedState->lastPong)
8547 		    ScheduleDelayedEvent(DeferredBookMove, 10);
8548 	else
8549 	HandleMachineMove(savedMessage, savedState);
8550 }
8551 
8552 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8553 static ChessProgramState *stalledEngine;
8554 static char stashedInputMove[MSG_SIZ];
8555 
8556 void
HandleMachineMove(char * message,ChessProgramState * cps)8557 HandleMachineMove (char *message, ChessProgramState *cps)
8558 {
8559     static char firstLeg[20];
8560     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8561     char realname[MSG_SIZ];
8562     int fromX, fromY, toX, toY;
8563     ChessMove moveType;
8564     char promoChar, roar;
8565     char *p, *pv=buf1;
8566     int machineWhite, oldError;
8567     char *bookHit;
8568 
8569     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8570 	// [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8571 	if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8572 	    DisplayError(_("Invalid pairing from pairing engine"), 0);
8573 	    return;
8574 	}
8575 	pairingReceived = 1;
8576 	NextMatchGame();
8577 	return; // Skim the pairing messages here.
8578     }
8579 
8580     oldError = cps->userError; cps->userError = 0;
8581 
8582 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8583     /*
8584      * Kludge to ignore BEL characters
8585      */
8586     while (*message == '\007') message++;
8587 
8588     /*
8589      * [HGM] engine debug message: ignore lines starting with '#' character
8590      */
8591     if(cps->debug && *message == '#') return;
8592 
8593     /*
8594      * Look for book output
8595      */
8596     if (cps == &first && bookRequested) {
8597 	if (message[0] == '\t' || message[0] == ' ') {
8598 	    /* Part of the book output is here; append it */
8599 	    strcat(bookOutput, message);
8600 	    strcat(bookOutput, "  \n");
8601 	    return;
8602 	} else if (bookOutput[0] != NULLCHAR) {
8603 	    /* All of book output has arrived; display it */
8604 	    char *p = bookOutput;
8605 	    while (*p != NULLCHAR) {
8606 		if (*p == '\t') *p = ' ';
8607 		p++;
8608 	    }
8609 	    DisplayInformation(bookOutput);
8610 	    bookRequested = FALSE;
8611 	    /* Fall through to parse the current output */
8612 	}
8613     }
8614 
8615     /*
8616      * Look for machine move.
8617      */
8618     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8619 	(sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8620     {
8621         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8622 	    if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8623 	    safeStrCpy(stashedInputMove, message, MSG_SIZ);
8624 	    stalledEngine = cps;
8625 	    if(appData.ponderNextMove) { // bring opponent out of ponder
8626 		if(gameMode == TwoMachinesPlay) {
8627 		    if(cps->other->pause)
8628 			PauseEngine(cps->other);
8629 		    else
8630 			SendToProgram("easy\n", cps->other);
8631 		}
8632 	    }
8633 	    StopClocks();
8634 	    return;
8635 	}
8636 
8637         /* This method is only useful on engines that support ping */
8638         if (cps->lastPing != cps->lastPong) {
8639 	  if (gameMode == BeginningOfGame) {
8640 	    /* Extra move from before last new; ignore */
8641 	    if (appData.debugMode) {
8642 		fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8643 	    }
8644 	  } else {
8645 	    if (appData.debugMode) {
8646 		fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8647 			cps->which, gameMode);
8648 	    }
8649 
8650             SendToProgram("undo\n", cps);
8651 	  }
8652 	  return;
8653 	}
8654 
8655 	switch (gameMode) {
8656 	  case BeginningOfGame:
8657 	    /* Extra move from before last reset; ignore */
8658 	    if (appData.debugMode) {
8659 		fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8660 	    }
8661 	    return;
8662 
8663 	  case EndOfGame:
8664 	  case IcsIdle:
8665 	  default:
8666 	    /* Extra move after we tried to stop.  The mode test is
8667 	       not a reliable way of detecting this problem, but it's
8668 	       the best we can do on engines that don't support ping.
8669 	    */
8670 	    if (appData.debugMode) {
8671 		fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8672 			cps->which, gameMode);
8673 	    }
8674 	    SendToProgram("undo\n", cps);
8675 	    return;
8676 
8677 	  case MachinePlaysWhite:
8678 	  case IcsPlayingWhite:
8679 	    machineWhite = TRUE;
8680 	    break;
8681 
8682 	  case MachinePlaysBlack:
8683 	  case IcsPlayingBlack:
8684 	    machineWhite = FALSE;
8685 	    break;
8686 
8687 	  case TwoMachinesPlay:
8688 	    machineWhite = (cps->twoMachinesColor[0] == 'w');
8689 	    break;
8690 	}
8691 	if (WhiteOnMove(forwardMostMove) != machineWhite) {
8692 	    if (appData.debugMode) {
8693 		fprintf(debugFP,
8694 			"Ignoring move out of turn by %s, gameMode %d"
8695 			", forwardMost %d\n",
8696 			cps->which, gameMode, forwardMostMove);
8697 	    }
8698 	    return;
8699 	}
8700 
8701         if(cps->alphaRank) AlphaRank(machineMove, 4);
8702 
8703 	// [HGM] lion: (some very limited) support for Alien protocol
8704 	killX = killY = kill2X = kill2Y = -1;
8705 	if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8706 	    safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8707 	    return;
8708 	}
8709 	if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8710 	    safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8711 	    safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8712 	}
8713 	if(firstLeg[0]) { // there was a previous leg;
8714 	    // only support case where same piece makes two step
8715 	    char buf[20], *p = machineMove+1, *q = buf+1, f;
8716 	    safeStrCpy(buf, machineMove, 20);
8717 	    while(isdigit(*q)) q++; // find start of to-square
8718 	    safeStrCpy(machineMove, firstLeg, 20);
8719 	    while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8720 	    if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8721 	    safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8722 	    sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8723 	    firstLeg[0] = NULLCHAR;
8724 	}
8725 
8726         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8727                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8728 	    /* Machine move could not be parsed; ignore it. */
8729 	  snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8730 		    machineMove, _(cps->which));
8731 	    DisplayMoveError(buf1);
8732             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8733                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8734 	    if (gameMode == TwoMachinesPlay) {
8735 	      GameEnds(machineWhite ? BlackWins : WhiteWins,
8736                        buf1, GE_XBOARD);
8737 	    }
8738 	    return;
8739 	}
8740 
8741         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8742         /* So we have to redo legality test with true e.p. status here,  */
8743         /* to make sure an illegal e.p. capture does not slip through,   */
8744         /* to cause a forfeit on a justified illegal-move complaint      */
8745         /* of the opponent.                                              */
8746         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8747            ChessMove moveType;
8748            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8749                              fromY, fromX, toY, toX, promoChar);
8750             if(moveType == IllegalMove) {
8751 	      snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8752                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8753                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8754                            buf1, GE_XBOARD);
8755 		return;
8756            } else if(!appData.fischerCastling)
8757            /* [HGM] Kludge to handle engines that send FRC-style castling
8758               when they shouldn't (like TSCP-Gothic) */
8759            switch(moveType) {
8760              case WhiteASideCastleFR:
8761              case BlackASideCastleFR:
8762                toX+=2;
8763                currentMoveString[2]++;
8764                break;
8765              case WhiteHSideCastleFR:
8766              case BlackHSideCastleFR:
8767                toX--;
8768                currentMoveString[2]--;
8769                break;
8770 	     default: ; // nothing to do, but suppresses warning of pedantic compilers
8771            }
8772         }
8773 	hintRequested = FALSE;
8774 	lastHint[0] = NULLCHAR;
8775 	bookRequested = FALSE;
8776 	/* Program may be pondering now */
8777 	cps->maybeThinking = TRUE;
8778 	if (cps->sendTime == 2) cps->sendTime = 1;
8779 	if (cps->offeredDraw) cps->offeredDraw--;
8780 
8781         /* [AS] Save move info*/
8782         pvInfoList[ forwardMostMove ].score = programStats.score;
8783         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8784         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8785 
8786 	MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8787 
8788         /* Test suites abort the 'game' after one move */
8789         if(*appData.finger) {
8790            static FILE *f;
8791            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8792            if(!f) f = fopen(appData.finger, "w");
8793            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8794            else { DisplayFatalError("Bad output file", errno, 0); return; }
8795            free(fen);
8796            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8797         }
8798         if(appData.epd) {
8799            if(solvingTime >= 0) {
8800               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8801               totalTime += solvingTime; first.matchWins++;
8802            } else {
8803               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8804               second.matchWins++;
8805            }
8806            OutputKibitz(2, buf1);
8807            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8808         }
8809 
8810         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8811         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8812             int count = 0;
8813 
8814             while( count < adjudicateLossPlies ) {
8815                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8816 
8817                 if( count & 1 ) {
8818                     score = -score; /* Flip score for winning side */
8819                 }
8820 
8821                 if( score > appData.adjudicateLossThreshold ) {
8822                     break;
8823                 }
8824 
8825                 count++;
8826             }
8827 
8828             if( count >= adjudicateLossPlies ) {
8829 	        ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8830 
8831                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8832                     "Xboard adjudication",
8833                     GE_XBOARD );
8834 
8835                 return;
8836             }
8837         }
8838 
8839 	if(Adjudicate(cps)) {
8840 	    ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8841 	    return; // [HGM] adjudicate: for all automatic game ends
8842 	}
8843 
8844 #if ZIPPY
8845 	if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8846 	    first.initDone) {
8847 	  if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8848 		SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8849 		SendToICS("draw ");
8850 		SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8851 	  }
8852 	  SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8853 	  ics_user_moved = 1;
8854 	  if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8855 		char buf[3*MSG_SIZ];
8856 
8857 		snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8858 			programStats.score / 100.,
8859 			programStats.depth,
8860 			programStats.time / 100.,
8861 			(unsigned int)programStats.nodes,
8862 			(unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8863 			programStats.movelist);
8864 		SendToICS(buf);
8865 	  }
8866 	}
8867 #endif
8868 
8869         /* [AS] Clear stats for next move */
8870         ClearProgramStats();
8871         thinkOutput[0] = NULLCHAR;
8872         hiddenThinkOutputState = 0;
8873 
8874 	bookHit = NULL;
8875 	if (gameMode == TwoMachinesPlay) {
8876             /* [HGM] relaying draw offers moved to after reception of move */
8877             /* and interpreting offer as claim if it brings draw condition */
8878             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8879                 SendToProgram("draw\n", cps->other);
8880             }
8881 	    if (cps->other->sendTime) {
8882 		SendTimeRemaining(cps->other,
8883 				  cps->other->twoMachinesColor[0] == 'w');
8884 	    }
8885 	    bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8886 	    if (firstMove && !bookHit) {
8887 		firstMove = FALSE;
8888 		if (cps->other->useColors) {
8889 		  SendToProgram(cps->other->twoMachinesColor, cps->other);
8890 		}
8891 		SendToProgram("go\n", cps->other);
8892 	    }
8893 	    cps->other->maybeThinking = TRUE;
8894 	}
8895 
8896 	roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8897 
8898 	ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8899 
8900         if (!pausing && appData.ringBellAfterMoves) {
8901 	    if(!roar) RingBell();
8902 	}
8903 
8904 	/*
8905 	 * Reenable menu items that were disabled while
8906 	 * machine was thinking
8907 	 */
8908 	if (gameMode != TwoMachinesPlay)
8909 	    SetUserThinkingEnables();
8910 
8911 	// [HGM] book: after book hit opponent has received move and is now in force mode
8912 	// force the book reply into it, and then fake that it outputted this move by jumping
8913 	// back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8914 	if(bookHit) {
8915 		static char bookMove[MSG_SIZ]; // a bit generous?
8916 
8917 		safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8918 		strcat(bookMove, bookHit);
8919 		message = bookMove;
8920 		cps = cps->other;
8921 		programStats.nodes = programStats.depth = programStats.time =
8922 		programStats.score = programStats.got_only_move = 0;
8923 		sprintf(programStats.movelist, "%s (xbook)", bookHit);
8924 
8925 		if(cps->lastPing != cps->lastPong) {
8926 		    savedMessage = message; // args for deferred call
8927 		    savedState = cps;
8928 		    ScheduleDelayedEvent(DeferredBookMove, 10);
8929 		    return;
8930 		}
8931 		goto FakeBookMove;
8932 	}
8933 
8934 	return;
8935     }
8936 
8937     /* Set special modes for chess engines.  Later something general
8938      *  could be added here; for now there is just one kludge feature,
8939      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8940      *  when "xboard" is given as an interactive command.
8941      */
8942     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8943 	cps->useSigint = FALSE;
8944 	cps->useSigterm = FALSE;
8945     }
8946     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8947       ParseFeatures(message+8, cps);
8948       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8949     }
8950 
8951     if (!strncmp(message, "setup ", 6) &&
8952 	(!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8953           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8954 					) { // [HGM] allow first engine to define opening position
8955       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8956       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8957       *buf = NULLCHAR;
8958       if(sscanf(message, "setup (%s", buf) == 1) {
8959         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8960         ASSIGN(appData.pieceToCharTable, buf);
8961       }
8962       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8963       if(dummy >= 3) {
8964         while(message[s] && message[s++] != ' ');
8965         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8966            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8967 	    appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8968 	    if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8969           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8970           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8971           startedFromSetupPosition = FALSE;
8972         }
8973       }
8974       if(startedFromSetupPosition) return;
8975       ParseFEN(boards[0], &dummy, message+s, FALSE);
8976       DrawPosition(TRUE, boards[0]);
8977       CopyBoard(initialPosition, boards[0]);
8978       startedFromSetupPosition = TRUE;
8979       return;
8980     }
8981     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8982       ChessSquare piece = WhitePawn;
8983       char *p=buf2, *q, *s = SUFFIXES, ID = *p;
8984       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8985       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8986       piece += CharToPiece(ID) - WhitePawn;
8987       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8988       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8989       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8990       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8991       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8992       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8993                                                && gameInfo.variant != VariantGreat
8994                                                && gameInfo.variant != VariantFairy    ) return;
8995       if(piece < EmptySquare) {
8996         pieceDefs = TRUE;
8997         ASSIGN(pieceDesc[piece], buf1);
8998         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8999       }
9000       return;
9001     }
9002     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9003      * want this, I was asked to put it in, and obliged.
9004      */
9005     if (!strncmp(message, "setboard ", 9)) {
9006         Board initial_position;
9007 
9008         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9009 
9010         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9011             DisplayError(_("Bad FEN received from engine"), 0);
9012             return ;
9013         } else {
9014            Reset(TRUE, FALSE);
9015            CopyBoard(boards[0], initial_position);
9016            initialRulePlies = FENrulePlies;
9017            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9018            else gameMode = MachinePlaysBlack;
9019            DrawPosition(FALSE, boards[currentMove]);
9020         }
9021 	return;
9022     }
9023 
9024     /*
9025      * Look for communication commands
9026      */
9027     if (!strncmp(message, "telluser ", 9)) {
9028 	if(message[9] == '\\' && message[10] == '\\')
9029 	    EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9030 	PlayTellSound();
9031 	DisplayNote(message + 9);
9032 	return;
9033     }
9034     if (!strncmp(message, "tellusererror ", 14)) {
9035 	cps->userError = 1;
9036 	if(message[14] == '\\' && message[15] == '\\')
9037 	    EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9038 	PlayTellSound();
9039 	DisplayError(message + 14, 0);
9040 	return;
9041     }
9042     if (!strncmp(message, "tellopponent ", 13)) {
9043       if (appData.icsActive) {
9044 	if (loggedOn) {
9045 	  snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9046 	  SendToICS(buf1);
9047 	}
9048       } else {
9049 	DisplayNote(message + 13);
9050       }
9051       return;
9052     }
9053     if (!strncmp(message, "tellothers ", 11)) {
9054       if (appData.icsActive) {
9055 	if (loggedOn) {
9056 	  snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9057 	  SendToICS(buf1);
9058 	}
9059       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9060       return;
9061     }
9062     if (!strncmp(message, "tellall ", 8)) {
9063       if (appData.icsActive) {
9064 	if (loggedOn) {
9065 	  snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9066 	  SendToICS(buf1);
9067 	}
9068       } else {
9069 	DisplayNote(message + 8);
9070       }
9071       return;
9072     }
9073     if (strncmp(message, "warning", 7) == 0) {
9074 	/* Undocumented feature, use tellusererror in new code */
9075 	DisplayError(message, 0);
9076 	return;
9077     }
9078     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9079         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9080 	strcat(realname, " query");
9081 	AskQuestion(realname, buf2, buf1, cps->pr);
9082 	return;
9083     }
9084     /* Commands from the engine directly to ICS.  We don't allow these to be
9085      *  sent until we are logged on. Crafty kibitzes have been known to
9086      *  interfere with the login process.
9087      */
9088     if (loggedOn) {
9089 	if (!strncmp(message, "tellics ", 8)) {
9090 	    SendToICS(message + 8);
9091 	    SendToICS("\n");
9092 	    return;
9093 	}
9094 	if (!strncmp(message, "tellicsnoalias ", 15)) {
9095 	    SendToICS(ics_prefix);
9096 	    SendToICS(message + 15);
9097 	    SendToICS("\n");
9098 	    return;
9099 	}
9100 	/* The following are for backward compatibility only */
9101 	if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9102 	    !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9103 	    SendToICS(ics_prefix);
9104 	    SendToICS(message);
9105 	    SendToICS("\n");
9106 	    return;
9107 	}
9108     }
9109     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9110 	if(initPing == cps->lastPong) {
9111 	    if(gameInfo.variant == VariantUnknown) {
9112 		DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9113 		*engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9114 		GameEnds(GameUnfinished, NULL, GE_XBOARD);
9115 	    }
9116 	    initPing = -1;
9117         }
9118 	return;
9119     }
9120     if(!strncmp(message, "highlight ", 10)) {
9121 	if(appData.testLegality && !*engineVariant && appData.markers) return;
9122 	MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9123 	return;
9124     }
9125     if(!strncmp(message, "click ", 6)) {
9126 	char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9127 	if(appData.testLegality || !appData.oneClick) return;
9128 	sscanf(message+6, "%c%d%c", &f, &y, &c);
9129 	x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9130 	if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9131 	x = x*squareSize + (x+1)*lineGap + squareSize/2;
9132 	y = y*squareSize + (y+1)*lineGap + squareSize/2;
9133 	f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9134 	if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9135 	    LeftClick(Release, lastLeftX, lastLeftY);
9136 	controlKey  = (c == ',');
9137 	LeftClick(Press, x, y);
9138 	LeftClick(Release, x, y);
9139 	first.highlight = f;
9140 	return;
9141     }
9142     /*
9143      * If the move is illegal, cancel it and redraw the board.
9144      * Also deal with other error cases.  Matching is rather loose
9145      * here to accommodate engines written before the spec.
9146      */
9147     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9148 	strncmp(message, "Error", 5) == 0) {
9149 	if (StrStr(message, "name") ||
9150 	    StrStr(message, "rating") || StrStr(message, "?") ||
9151 	    StrStr(message, "result") || StrStr(message, "board") ||
9152 	    StrStr(message, "bk") || StrStr(message, "computer") ||
9153 	    StrStr(message, "variant") || StrStr(message, "hint") ||
9154 	    StrStr(message, "random") || StrStr(message, "depth") ||
9155 	    StrStr(message, "accepted")) {
9156 	    return;
9157 	}
9158 	if (StrStr(message, "protover")) {
9159 	  /* Program is responding to input, so it's apparently done
9160              initializing, and this error message indicates it is
9161              protocol version 1.  So we don't need to wait any longer
9162              for it to initialize and send feature commands. */
9163 	  FeatureDone(cps, 1);
9164 	  cps->protocolVersion = 1;
9165 	  return;
9166 	}
9167 	cps->maybeThinking = FALSE;
9168 
9169 	if (StrStr(message, "draw")) {
9170 	    /* Program doesn't have "draw" command */
9171 	    cps->sendDrawOffers = 0;
9172 	    return;
9173 	}
9174 	if (cps->sendTime != 1 &&
9175 	    (StrStr(message, "time") || StrStr(message, "otim"))) {
9176 	  /* Program apparently doesn't have "time" or "otim" command */
9177 	  cps->sendTime = 0;
9178 	  return;
9179 	}
9180 	if (StrStr(message, "analyze")) {
9181 	    cps->analysisSupport = FALSE;
9182 	    cps->analyzing = FALSE;
9183 //	    Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9184 	    EditGameEvent(); // [HGM] try to preserve loaded game
9185 	    snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9186 	    DisplayError(buf2, 0);
9187 	    return;
9188 	}
9189 	if (StrStr(message, "(no matching move)st")) {
9190 	  /* Special kludge for GNU Chess 4 only */
9191 	  cps->stKludge = TRUE;
9192 	  SendTimeControl(cps, movesPerSession, timeControl,
9193 			  timeIncrement, appData.searchDepth,
9194 			  searchTime);
9195 	  return;
9196 	}
9197 	if (StrStr(message, "(no matching move)sd")) {
9198 	  /* Special kludge for GNU Chess 4 only */
9199 	  cps->sdKludge = TRUE;
9200 	  SendTimeControl(cps, movesPerSession, timeControl,
9201 			  timeIncrement, appData.searchDepth,
9202 			  searchTime);
9203 	  return;
9204 	}
9205         if (!StrStr(message, "llegal")) {
9206             return;
9207         }
9208 	if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9209 	    gameMode == IcsIdle) return;
9210 	if (forwardMostMove <= backwardMostMove) return;
9211 	if (pausing) PauseEvent();
9212       if(appData.forceIllegal) {
9213 	    // [HGM] illegal: machine refused move; force position after move into it
9214           SendToProgram("force\n", cps);
9215           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9216 		// we have a real problem now, as SendBoard will use the a2a3 kludge
9217 		// when black is to move, while there might be nothing on a2 or black
9218 		// might already have the move. So send the board as if white has the move.
9219 		// But first we must change the stm of the engine, as it refused the last move
9220 		SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9221 		if(WhiteOnMove(forwardMostMove)) {
9222 		    SendToProgram("a7a6\n", cps); // for the engine black still had the move
9223 		    SendBoard(cps, forwardMostMove); // kludgeless board
9224 		} else {
9225 		    SendToProgram("a2a3\n", cps); // for the engine white still had the move
9226 		    CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9227 		    SendBoard(cps, forwardMostMove+1); // kludgeless board
9228 		}
9229           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9230 	    if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9231 		 gameMode == TwoMachinesPlay)
9232               SendToProgram("go\n", cps);
9233 	    return;
9234       } else
9235 	if (gameMode == PlayFromGameFile) {
9236 	    /* Stop reading this game file */
9237 	    gameMode = EditGame;
9238 	    ModeHighlight();
9239 	}
9240         /* [HGM] illegal-move claim should forfeit game when Xboard */
9241         /* only passes fully legal moves                            */
9242         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9243             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9244                                 "False illegal-move claim", GE_XBOARD );
9245             return; // do not take back move we tested as valid
9246         }
9247 	currentMove = forwardMostMove-1;
9248 	DisplayMove(currentMove-1); /* before DisplayMoveError */
9249 	SwitchClocks(forwardMostMove-1); // [HGM] race
9250 	DisplayBothClocks();
9251 	snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9252 		parseList[currentMove], _(cps->which));
9253 	DisplayMoveError(buf1);
9254 	DrawPosition(FALSE, boards[currentMove]);
9255 
9256 	SetUserThinkingEnables();
9257 	return;
9258     }
9259     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9260 	/* Program has a broken "time" command that
9261 	   outputs a string not ending in newline.
9262 	   Don't use it. */
9263 	cps->sendTime = 0;
9264     }
9265     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9266 	if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9267 	    sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9268     }
9269 
9270     /*
9271      * If chess program startup fails, exit with an error message.
9272      * Attempts to recover here are futile. [HGM] Well, we try anyway
9273      */
9274     if ((StrStr(message, "unknown host") != NULL)
9275 	|| (StrStr(message, "No remote directory") != NULL)
9276 	|| (StrStr(message, "not found") != NULL)
9277 	|| (StrStr(message, "No such file") != NULL)
9278 	|| (StrStr(message, "can't alloc") != NULL)
9279 	|| (StrStr(message, "Permission denied") != NULL)) {
9280 
9281 	cps->maybeThinking = FALSE;
9282 	snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9283 		_(cps->which), cps->program, cps->host, message);
9284 	RemoveInputSource(cps->isr);
9285 	if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9286 	    if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9287 	    if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9288 	}
9289 	return;
9290     }
9291 
9292     /*
9293      * Look for hint output
9294      */
9295     if (sscanf(message, "Hint: %s", buf1) == 1) {
9296 	if (cps == &first && hintRequested) {
9297 	    hintRequested = FALSE;
9298 	    if (ParseOneMove(buf1, forwardMostMove, &moveType,
9299 				 &fromX, &fromY, &toX, &toY, &promoChar)) {
9300 		(void) CoordsToAlgebraic(boards[forwardMostMove],
9301 				    PosFlags(forwardMostMove),
9302 				    fromY, fromX, toY, toX, promoChar, buf1);
9303 		snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9304 		DisplayInformation(buf2);
9305 	    } else {
9306 		/* Hint move could not be parsed!? */
9307 	      snprintf(buf2, sizeof(buf2),
9308 			_("Illegal hint move \"%s\"\nfrom %s chess program"),
9309 			buf1, _(cps->which));
9310 		DisplayError(buf2, 0);
9311 	    }
9312 	} else {
9313 	  safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9314 	}
9315 	return;
9316     }
9317 
9318     /*
9319      * Ignore other messages if game is not in progress
9320      */
9321     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9322 	gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9323 
9324     /*
9325      * look for win, lose, draw, or draw offer
9326      */
9327     if (strncmp(message, "1-0", 3) == 0) {
9328 	char *p, *q, *r = "";
9329         p = strchr(message, '{');
9330 	if (p) {
9331 	    q = strchr(p, '}');
9332 	    if (q) {
9333 		*q = NULLCHAR;
9334 		r = p + 1;
9335 	    }
9336 	}
9337         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9338 	return;
9339     } else if (strncmp(message, "0-1", 3) == 0) {
9340 	char *p, *q, *r = "";
9341         p = strchr(message, '{');
9342 	if (p) {
9343 	    q = strchr(p, '}');
9344 	    if (q) {
9345 		*q = NULLCHAR;
9346 		r = p + 1;
9347 	    }
9348 	}
9349 	/* Kludge for Arasan 4.1 bug */
9350 	if (strcmp(r, "Black resigns") == 0) {
9351             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9352 	    return;
9353 	}
9354         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9355 	return;
9356     } else if (strncmp(message, "1/2", 3) == 0) {
9357 	char *p, *q, *r = "";
9358         p = strchr(message, '{');
9359 	if (p) {
9360 	    q = strchr(p, '}');
9361 	    if (q) {
9362 		*q = NULLCHAR;
9363 		r = p + 1;
9364 	    }
9365 	}
9366 
9367         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9368 	return;
9369 
9370     } else if (strncmp(message, "White resign", 12) == 0) {
9371         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9372 	return;
9373     } else if (strncmp(message, "Black resign", 12) == 0) {
9374         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9375 	return;
9376     } else if (strncmp(message, "White matches", 13) == 0 ||
9377                strncmp(message, "Black matches", 13) == 0   ) {
9378         /* [HGM] ignore GNUShogi noises */
9379         return;
9380     } else if (strncmp(message, "White", 5) == 0 &&
9381 	       message[5] != '(' &&
9382 	       StrStr(message, "Black") == NULL) {
9383         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9384 	return;
9385     } else if (strncmp(message, "Black", 5) == 0 &&
9386 	       message[5] != '(') {
9387         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9388 	return;
9389     } else if (strcmp(message, "resign") == 0 ||
9390 	       strcmp(message, "computer resigns") == 0) {
9391 	switch (gameMode) {
9392 	  case MachinePlaysBlack:
9393 	  case IcsPlayingBlack:
9394             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9395 	    break;
9396 	  case MachinePlaysWhite:
9397 	  case IcsPlayingWhite:
9398             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9399 	    break;
9400 	  case TwoMachinesPlay:
9401 	    if (cps->twoMachinesColor[0] == 'w')
9402               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9403 	    else
9404               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9405 	    break;
9406 	  default:
9407 	    /* can't happen */
9408 	    break;
9409 	}
9410 	return;
9411     } else if (strncmp(message, "opponent mates", 14) == 0) {
9412 	switch (gameMode) {
9413 	  case MachinePlaysBlack:
9414 	  case IcsPlayingBlack:
9415             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9416 	    break;
9417 	  case MachinePlaysWhite:
9418 	  case IcsPlayingWhite:
9419             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9420 	    break;
9421 	  case TwoMachinesPlay:
9422 	    if (cps->twoMachinesColor[0] == 'w')
9423               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9424 	    else
9425               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9426 	    break;
9427 	  default:
9428 	    /* can't happen */
9429 	    break;
9430 	}
9431 	return;
9432     } else if (strncmp(message, "computer mates", 14) == 0) {
9433 	switch (gameMode) {
9434 	  case MachinePlaysBlack:
9435 	  case IcsPlayingBlack:
9436             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9437 	    break;
9438 	  case MachinePlaysWhite:
9439 	  case IcsPlayingWhite:
9440             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9441 	    break;
9442 	  case TwoMachinesPlay:
9443 	    if (cps->twoMachinesColor[0] == 'w')
9444               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9445 	    else
9446               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9447 	    break;
9448 	  default:
9449 	    /* can't happen */
9450 	    break;
9451 	}
9452 	return;
9453     } else if (strncmp(message, "checkmate", 9) == 0) {
9454 	if (WhiteOnMove(forwardMostMove)) {
9455             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9456 	} else {
9457             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9458 	}
9459 	return;
9460     } else if (strstr(message, "Draw") != NULL ||
9461 	       strstr(message, "game is a draw") != NULL) {
9462         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9463 	return;
9464     } else if (strstr(message, "offer") != NULL &&
9465 	       strstr(message, "draw") != NULL) {
9466 #if ZIPPY
9467 	if (appData.zippyPlay && first.initDone) {
9468 	    /* Relay offer to ICS */
9469 	    SendToICS(ics_prefix);
9470 	    SendToICS("draw\n");
9471 	}
9472 #endif
9473 	cps->offeredDraw = 2; /* valid until this engine moves twice */
9474 	if (gameMode == TwoMachinesPlay) {
9475 	    if (cps->other->offeredDraw) {
9476 		GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9477             /* [HGM] in two-machine mode we delay relaying draw offer      */
9478             /* until after we also have move, to see if it is really claim */
9479 	    }
9480 	} else if (gameMode == MachinePlaysWhite ||
9481 		   gameMode == MachinePlaysBlack) {
9482 	  if (userOfferedDraw) {
9483 	    DisplayInformation(_("Machine accepts your draw offer"));
9484 	    GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9485 	  } else {
9486             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9487 	  }
9488 	}
9489     }
9490 
9491 
9492     /*
9493      * Look for thinking output
9494      */
9495     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9496 	  || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9497 				) {
9498 	int plylev, mvleft, mvtot, curscore, time;
9499 	char mvname[MOVE_LEN];
9500 	u64 nodes; // [DM]
9501 	char plyext;
9502 	int ignore = FALSE;
9503 	int prefixHint = FALSE;
9504 	mvname[0] = NULLCHAR;
9505 
9506 	switch (gameMode) {
9507 	  case MachinePlaysBlack:
9508 	  case IcsPlayingBlack:
9509 	    if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9510 	    break;
9511 	  case MachinePlaysWhite:
9512 	  case IcsPlayingWhite:
9513 	    if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9514 	    break;
9515 	  case AnalyzeMode:
9516 	  case AnalyzeFile:
9517             break;
9518           case IcsObserving: /* [DM] icsEngineAnalyze */
9519             if (!appData.icsEngineAnalyze) ignore = TRUE;
9520 	    break;
9521 	  case TwoMachinesPlay:
9522 	    if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9523 		ignore = TRUE;
9524 	    }
9525 	    break;
9526 	  default:
9527 	    ignore = TRUE;
9528 	    break;
9529 	}
9530 
9531 	if (!ignore) {
9532 	    ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9533 	    buf1[0] = NULLCHAR;
9534 	    if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9535 		       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9536 		char score_buf[MSG_SIZ];
9537 
9538 		if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9539 		    nodes += u64Const(0x100000000);
9540 
9541 		if (plyext != ' ' && plyext != '\t') {
9542 		    time *= 100;
9543 		}
9544 
9545                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9546                 if( cps->scoreIsAbsolute &&
9547                     ( gameMode == MachinePlaysBlack ||
9548                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9549                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9550                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9551                      !WhiteOnMove(currentMove)
9552                     ) )
9553                 {
9554                     curscore = -curscore;
9555                 }
9556 
9557 		if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9558 
9559 		if(*bestMove) { // rememer time best EPD move was first found
9560 		    int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9561 		    ChessMove mt;
9562 		    int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9563 		    ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9564 		    solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9565 		}
9566 
9567 		if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9568 			char buf[MSG_SIZ];
9569 			FILE *f;
9570 			snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9571 			buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9572 			                     gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9573 			if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9574 			if(f = fopen(buf, "w")) { // export PV to applicable PV file
9575 				fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9576 				fclose(f);
9577 			}
9578 			else
9579 			  /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9580 			  DisplayError(_("failed writing PV"), 0);
9581 		}
9582 
9583 		tempStats.depth = plylev;
9584 		tempStats.nodes = nodes;
9585 		tempStats.time = time;
9586 		tempStats.score = curscore;
9587 		tempStats.got_only_move = 0;
9588 
9589 		if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9590 			int ticklen;
9591 
9592 			if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9593 			else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9594 			if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9595 						gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9596 			     whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9597 			if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9598 						gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9599 			     blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9600 		}
9601 
9602 		/* Buffer overflow protection */
9603 		if (pv[0] != NULLCHAR) {
9604 		    if (strlen(pv) >= sizeof(tempStats.movelist)
9605 			&& appData.debugMode) {
9606 			fprintf(debugFP,
9607 				"PV is too long; using the first %u bytes.\n",
9608 				(unsigned) sizeof(tempStats.movelist) - 1);
9609 		    }
9610 
9611                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9612 		} else {
9613 		    sprintf(tempStats.movelist, " no PV\n");
9614 		}
9615 
9616 		if (tempStats.seen_stat) {
9617 		    tempStats.ok_to_send = 1;
9618 		}
9619 
9620 		if (strchr(tempStats.movelist, '(') != NULL) {
9621 		    tempStats.line_is_book = 1;
9622 		    tempStats.nr_moves = 0;
9623 		    tempStats.moves_left = 0;
9624 		} else {
9625 		    tempStats.line_is_book = 0;
9626 		}
9627 
9628 		    if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9629 			programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9630 
9631                 SendProgramStatsToFrontend( cps, &tempStats );
9632 
9633                 /*
9634                     [AS] Protect the thinkOutput buffer from overflow... this
9635                     is only useful if buf1 hasn't overflowed first!
9636                 */
9637 		if(curscore >= MATE_SCORE)
9638 		    snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9639 		else if(curscore <= -MATE_SCORE)
9640 		    snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9641 		else
9642 		    snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9643 		snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9644 			 plylev,
9645 			 (gameMode == TwoMachinesPlay ?
9646 			  ToUpper(cps->twoMachinesColor[0]) : ' '),
9647 			 score_buf,
9648 			 prefixHint ? lastHint : "",
9649 			 prefixHint ? " " : "" );
9650 
9651                 if( buf1[0] != NULLCHAR ) {
9652                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9653 
9654                     if( strlen(pv) > max_len ) {
9655 			if( appData.debugMode) {
9656 			    fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9657                         }
9658                         pv[max_len+1] = '\0';
9659                     }
9660 
9661                     strcat( thinkOutput, pv);
9662                 }
9663 
9664                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9665                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9666 		    DisplayMove(currentMove - 1);
9667 		}
9668 		return;
9669 
9670 	    } else if ((p=StrStr(message, "(only move)")) != NULL) {
9671 		/* crafty (9.25+) says "(only move) <move>"
9672 		 * if there is only 1 legal move
9673                  */
9674 		sscanf(p, "(only move) %s", buf1);
9675 		snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9676 		sprintf(programStats.movelist, "%s (only move)", buf1);
9677 		programStats.depth = 1;
9678 		programStats.nr_moves = 1;
9679 		programStats.moves_left = 1;
9680 		programStats.nodes = 1;
9681 		programStats.time = 1;
9682 		programStats.got_only_move = 1;
9683 
9684 		/* Not really, but we also use this member to
9685 		   mean "line isn't going to change" (Crafty
9686 		   isn't searching, so stats won't change) */
9687 		programStats.line_is_book = 1;
9688 
9689                 SendProgramStatsToFrontend( cps, &programStats );
9690 
9691 		if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9692                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9693 		    DisplayMove(currentMove - 1);
9694 		}
9695 		return;
9696 	    } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9697 			      &time, &nodes, &plylev, &mvleft,
9698 			      &mvtot, mvname) >= 5) {
9699 		/* The stat01: line is from Crafty (9.29+) in response
9700 		   to the "." command */
9701 		programStats.seen_stat = 1;
9702 		cps->maybeThinking = TRUE;
9703 
9704 		if (programStats.got_only_move || !appData.periodicUpdates)
9705 		  return;
9706 
9707 		programStats.depth = plylev;
9708 		programStats.time = time;
9709 		programStats.nodes = nodes;
9710 		programStats.moves_left = mvleft;
9711 		programStats.nr_moves = mvtot;
9712 		safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9713 		programStats.ok_to_send = 1;
9714                 programStats.movelist[0] = '\0';
9715 
9716                 SendProgramStatsToFrontend( cps, &programStats );
9717 
9718 		return;
9719 
9720 	    } else if (strncmp(message,"++",2) == 0) {
9721 		/* Crafty 9.29+ outputs this */
9722 		programStats.got_fail = 2;
9723 		return;
9724 
9725 	    } else if (strncmp(message,"--",2) == 0) {
9726 		/* Crafty 9.29+ outputs this */
9727 		programStats.got_fail = 1;
9728 		return;
9729 
9730 	    } else if (thinkOutput[0] != NULLCHAR &&
9731 		       strncmp(message, "    ", 4) == 0) {
9732                 unsigned message_len;
9733 
9734 	        p = message;
9735 		while (*p && *p == ' ') p++;
9736 
9737                 message_len = strlen( p );
9738 
9739                 /* [AS] Avoid buffer overflow */
9740                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9741 		    strcat(thinkOutput, " ");
9742 		    strcat(thinkOutput, p);
9743                 }
9744 
9745                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9746 		    strcat(programStats.movelist, " ");
9747 		    strcat(programStats.movelist, p);
9748                 }
9749 
9750 		if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9751                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9752 		    DisplayMove(currentMove - 1);
9753 		}
9754 		return;
9755 	    }
9756 	}
9757         else {
9758 	    buf1[0] = NULLCHAR;
9759 
9760 	    if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9761 		       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9762             {
9763                 ChessProgramStats cpstats;
9764 
9765 		if (plyext != ' ' && plyext != '\t') {
9766 		    time *= 100;
9767 		}
9768 
9769                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9770                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9771                     curscore = -curscore;
9772                 }
9773 
9774 		cpstats.depth = plylev;
9775 		cpstats.nodes = nodes;
9776 		cpstats.time = time;
9777 		cpstats.score = curscore;
9778 		cpstats.got_only_move = 0;
9779                 cpstats.movelist[0] = '\0';
9780 
9781 		if (buf1[0] != NULLCHAR) {
9782                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9783 		}
9784 
9785 		cpstats.ok_to_send = 0;
9786 		cpstats.line_is_book = 0;
9787 		cpstats.nr_moves = 0;
9788 		cpstats.moves_left = 0;
9789 
9790                 SendProgramStatsToFrontend( cps, &cpstats );
9791             }
9792         }
9793     }
9794 }
9795 
9796 
9797 /* Parse a game score from the character string "game", and
9798    record it as the history of the current game.  The game
9799    score is NOT assumed to start from the standard position.
9800    The display is not updated in any way.
9801    */
9802 void
ParseGameHistory(char * game)9803 ParseGameHistory (char *game)
9804 {
9805     ChessMove moveType;
9806     int fromX, fromY, toX, toY, boardIndex;
9807     char promoChar;
9808     char *p, *q;
9809     char buf[MSG_SIZ];
9810 
9811     if (appData.debugMode)
9812       fprintf(debugFP, "Parsing game history: %s\n", game);
9813 
9814     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9815     gameInfo.site = StrSave(appData.icsHost);
9816     gameInfo.date = PGNDate();
9817     gameInfo.round = StrSave("-");
9818 
9819     /* Parse out names of players */
9820     while (*game == ' ') game++;
9821     p = buf;
9822     while (*game != ' ') *p++ = *game++;
9823     *p = NULLCHAR;
9824     gameInfo.white = StrSave(buf);
9825     while (*game == ' ') game++;
9826     p = buf;
9827     while (*game != ' ' && *game != '\n') *p++ = *game++;
9828     *p = NULLCHAR;
9829     gameInfo.black = StrSave(buf);
9830 
9831     /* Parse moves */
9832     boardIndex = blackPlaysFirst ? 1 : 0;
9833     yynewstr(game);
9834     for (;;) {
9835 	yyboardindex = boardIndex;
9836 	moveType = (ChessMove) Myylex();
9837 	switch (moveType) {
9838 	  case IllegalMove:		/* maybe suicide chess, etc. */
9839   if (appData.debugMode) {
9840     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9841     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9842     setbuf(debugFP, NULL);
9843   }
9844 	  case WhitePromotion:
9845 	  case BlackPromotion:
9846 	  case WhiteNonPromotion:
9847 	  case BlackNonPromotion:
9848 	  case NormalMove:
9849 	  case FirstLeg:
9850 	  case WhiteCapturesEnPassant:
9851 	  case BlackCapturesEnPassant:
9852 	  case WhiteKingSideCastle:
9853 	  case WhiteQueenSideCastle:
9854 	  case BlackKingSideCastle:
9855 	  case BlackQueenSideCastle:
9856 	  case WhiteKingSideCastleWild:
9857 	  case WhiteQueenSideCastleWild:
9858 	  case BlackKingSideCastleWild:
9859 	  case BlackQueenSideCastleWild:
9860           /* PUSH Fabien */
9861           case WhiteHSideCastleFR:
9862           case WhiteASideCastleFR:
9863           case BlackHSideCastleFR:
9864           case BlackASideCastleFR:
9865           /* POP Fabien */
9866             fromX = currentMoveString[0] - AAA;
9867             fromY = currentMoveString[1] - ONE;
9868             toX = currentMoveString[2] - AAA;
9869             toY = currentMoveString[3] - ONE;
9870 	    promoChar = currentMoveString[4];
9871 	    break;
9872 	  case WhiteDrop:
9873 	  case BlackDrop:
9874 	    if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9875 	    fromX = moveType == WhiteDrop ?
9876 	      (int) CharToPiece(ToUpper(currentMoveString[0])) :
9877 	    (int) CharToPiece(ToLower(currentMoveString[0]));
9878 	    fromY = DROP_RANK;
9879             toX = currentMoveString[2] - AAA;
9880             toY = currentMoveString[3] - ONE;
9881 	    promoChar = NULLCHAR;
9882 	    break;
9883 	  case AmbiguousMove:
9884 	    /* bug? */
9885 	    snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9886   if (appData.debugMode) {
9887     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9888     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9889     setbuf(debugFP, NULL);
9890   }
9891 	    DisplayError(buf, 0);
9892 	    return;
9893 	  case ImpossibleMove:
9894 	    /* bug? */
9895 	    snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9896   if (appData.debugMode) {
9897     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9898     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9899     setbuf(debugFP, NULL);
9900   }
9901 	    DisplayError(buf, 0);
9902 	    return;
9903 	  case EndOfFile:
9904 	    if (boardIndex < backwardMostMove) {
9905 		/* Oops, gap.  How did that happen? */
9906 		DisplayError(_("Gap in move list"), 0);
9907 		return;
9908 	    }
9909 	    backwardMostMove =  blackPlaysFirst ? 1 : 0;
9910 	    if (boardIndex > forwardMostMove) {
9911 		forwardMostMove = boardIndex;
9912 	    }
9913 	    return;
9914 	  case ElapsedTime:
9915 	    if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9916 		strcat(parseList[boardIndex-1], " ");
9917 		strcat(parseList[boardIndex-1], yy_text);
9918 	    }
9919 	    continue;
9920 	  case Comment:
9921 	  case PGNTag:
9922 	  case NAG:
9923 	  default:
9924 	    /* ignore */
9925 	    continue;
9926 	  case WhiteWins:
9927 	  case BlackWins:
9928 	  case GameIsDrawn:
9929 	  case GameUnfinished:
9930 	    if (gameMode == IcsExamining) {
9931 		if (boardIndex < backwardMostMove) {
9932 		    /* Oops, gap.  How did that happen? */
9933 		    return;
9934 		}
9935 		backwardMostMove = blackPlaysFirst ? 1 : 0;
9936 		return;
9937 	    }
9938 	    gameInfo.result = moveType;
9939 	    p = strchr(yy_text, '{');
9940 	    if (p == NULL) p = strchr(yy_text, '(');
9941 	    if (p == NULL) {
9942 		p = yy_text;
9943 		if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9944 	    } else {
9945 		q = strchr(p, *p == '{' ? '}' : ')');
9946 		if (q != NULL) *q = NULLCHAR;
9947 		p++;
9948 	    }
9949 	    while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9950 	    gameInfo.resultDetails = StrSave(p);
9951 	    continue;
9952 	}
9953 	if (boardIndex >= forwardMostMove &&
9954 	    !(gameMode == IcsObserving && ics_gamenum == -1)) {
9955 	    backwardMostMove = blackPlaysFirst ? 1 : 0;
9956 	    return;
9957 	}
9958 	(void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9959 				 fromY, fromX, toY, toX, promoChar,
9960 				 parseList[boardIndex]);
9961 	CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9962 	/* currentMoveString is set as a side-effect of yylex */
9963 	safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9964 	strcat(moveList[boardIndex], "\n");
9965 	boardIndex++;
9966 	ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9967         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9968 	  case MT_NONE:
9969 	  case MT_STALEMATE:
9970 	  default:
9971 	    break;
9972 	  case MT_CHECK:
9973             if(!IS_SHOGI(gameInfo.variant))
9974                 strcat(parseList[boardIndex - 1], "+");
9975 	    break;
9976 	  case MT_CHECKMATE:
9977 	  case MT_STAINMATE:
9978 	    strcat(parseList[boardIndex - 1], "#");
9979 	    break;
9980 	}
9981     }
9982 }
9983 
9984 
9985 /* Apply a move to the given board  */
9986 void
ApplyMove(int fromX,int fromY,int toX,int toY,int promoChar,Board board)9987 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9988 {
9989   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
9990   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9991 
9992     /* [HGM] compute & store e.p. status and castling rights for new position */
9993     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9994 
9995       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9996       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9997       board[EP_STATUS] = EP_NONE;
9998       board[EP_FILE] = board[EP_RANK] = 100;
9999 
10000   if (fromY == DROP_RANK) {
10001 	/* must be first */
10002         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10003 	    board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10004 	    return;
10005 	}
10006         piece = board[toY][toX] = (ChessSquare) fromX;
10007   } else {
10008 //      ChessSquare victim;
10009       int i;
10010 
10011       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10012 //           victim = board[killY][killX],
10013            killed = board[killY][killX],
10014            board[killY][killX] = EmptySquare,
10015            board[EP_STATUS] = EP_CAPTURE;
10016            if( kill2X >= 0 && kill2Y >= 0)
10017              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10018       }
10019 
10020       if( board[toY][toX] != EmptySquare ) {
10021            board[EP_STATUS] = EP_CAPTURE;
10022            if( (fromX != toX || fromY != toY) && // not igui!
10023                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10024                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10025                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10026            }
10027       }
10028 
10029       pawn = board[fromY][fromX];
10030       if( pawn == WhiteLance || pawn == BlackLance ) {
10031            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10032                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10033                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10034            }
10035       }
10036       if( pawn == WhitePawn ) {
10037            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10038 	       board[EP_STATUS] = EP_PAWN_MOVE;
10039            if( toY-fromY>=2) {
10040                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10041                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10042 			gameInfo.variant != VariantBerolina || toX < fromX)
10043 	              board[EP_STATUS] = toX | berolina;
10044                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10045 			gameInfo.variant != VariantBerolina || toX > fromX)
10046 	              board[EP_STATUS] = toX;
10047 	   }
10048       } else
10049       if( pawn == BlackPawn ) {
10050            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10051 	       board[EP_STATUS] = EP_PAWN_MOVE;
10052            if( toY-fromY<= -2) {
10053                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10054                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10055 			gameInfo.variant != VariantBerolina || toX < fromX)
10056 	              board[EP_STATUS] = toX | berolina;
10057                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10058 			gameInfo.variant != VariantBerolina || toX > fromX)
10059 	              board[EP_STATUS] = toX;
10060 	   }
10061        }
10062 
10063        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10064        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10065        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10066        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10067 
10068        for(i=0; i<nrCastlingRights; i++) {
10069            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10070               board[CASTLING][i] == toX   && castlingRank[i] == toY
10071              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10072        }
10073 
10074        if(gameInfo.variant == VariantSChess) { // update virginity
10075 	   if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10076 	   if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10077 	   if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10078 	   if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10079        }
10080 
10081      if (fromX == toX && fromY == toY) return;
10082 
10083      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10084      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10085      if(gameInfo.variant == VariantKnightmate)
10086          king += (int) WhiteUnicorn - (int) WhiteKing;
10087 
10088     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10089        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10090 	board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10091 	board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10092         board[EP_STATUS] = EP_NONE; // capture was fake!
10093     } else
10094     /* Code added by Tord: */
10095     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10096     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10097         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10098       board[EP_STATUS] = EP_NONE; // capture was fake!
10099       board[fromY][fromX] = EmptySquare;
10100       board[toY][toX] = EmptySquare;
10101       if((toX > fromX) != (piece == WhiteRook)) {
10102         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10103       } else {
10104         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10105       }
10106     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10107                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10108       board[EP_STATUS] = EP_NONE;
10109       board[fromY][fromX] = EmptySquare;
10110       board[toY][toX] = EmptySquare;
10111       if((toX > fromX) != (piece == BlackRook)) {
10112         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10113       } else {
10114         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10115       }
10116     /* End of code added by Tord */
10117 
10118     } else if (board[fromY][fromX] == king
10119         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10120         && toY == fromY && toX > fromX+1) {
10121 	for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10122         board[fromY][toX-1] = board[fromY][rookX];
10123         board[fromY][rookX] = EmptySquare;
10124 	board[fromY][fromX] = EmptySquare;
10125         board[toY][toX] = king;
10126     } else if (board[fromY][fromX] == king
10127         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10128                && toY == fromY && toX < fromX-1) {
10129 	for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10130         board[fromY][toX+1] = board[fromY][rookX];
10131         board[fromY][rookX] = EmptySquare;
10132 	board[fromY][fromX] = EmptySquare;
10133         board[toY][toX] = king;
10134     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10135                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10136                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10137                ) {
10138 	/* white pawn promotion */
10139         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10140         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10141             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10142 	board[fromY][fromX] = EmptySquare;
10143     } else if ((fromY >= BOARD_HEIGHT>>1)
10144 	       && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10145 	       && (toX != fromX)
10146                && gameInfo.variant != VariantXiangqi
10147                && gameInfo.variant != VariantBerolina
10148 	       && (pawn == WhitePawn)
10149 	       && (board[toY][toX] == EmptySquare)) {
10150 	board[fromY][fromX] = EmptySquare;
10151 	board[toY][toX] = piece;
10152 	if(toY == epRank - 128 + 1)
10153 	    captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10154 	else
10155 	    captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10156     } else if ((fromY == BOARD_HEIGHT-4)
10157 	       && (toX == fromX)
10158                && gameInfo.variant == VariantBerolina
10159 	       && (board[fromY][fromX] == WhitePawn)
10160 	       && (board[toY][toX] == EmptySquare)) {
10161 	board[fromY][fromX] = EmptySquare;
10162 	board[toY][toX] = WhitePawn;
10163 	if(oldEP & EP_BEROLIN_A) {
10164 		captured = board[fromY][fromX-1];
10165 		board[fromY][fromX-1] = EmptySquare;
10166 	}else{	captured = board[fromY][fromX+1];
10167 		board[fromY][fromX+1] = EmptySquare;
10168 	}
10169     } else if (board[fromY][fromX] == king
10170         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10171                && toY == fromY && toX > fromX+1) {
10172 	for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10173         board[fromY][toX-1] = board[fromY][rookX];
10174         board[fromY][rookX] = EmptySquare;
10175 	board[fromY][fromX] = EmptySquare;
10176         board[toY][toX] = king;
10177     } else if (board[fromY][fromX] == king
10178         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10179                && toY == fromY && toX < fromX-1) {
10180 	for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10181         board[fromY][toX+1] = board[fromY][rookX];
10182         board[fromY][rookX] = EmptySquare;
10183 	board[fromY][fromX] = EmptySquare;
10184         board[toY][toX] = king;
10185     } else if (fromY == 7 && fromX == 3
10186 	       && board[fromY][fromX] == BlackKing
10187 	       && toY == 7 && toX == 5) {
10188 	board[fromY][fromX] = EmptySquare;
10189 	board[toY][toX] = BlackKing;
10190 	board[fromY][7] = EmptySquare;
10191 	board[toY][4] = BlackRook;
10192     } else if (fromY == 7 && fromX == 3
10193 	       && board[fromY][fromX] == BlackKing
10194 	       && toY == 7 && toX == 1) {
10195 	board[fromY][fromX] = EmptySquare;
10196 	board[toY][toX] = BlackKing;
10197 	board[fromY][0] = EmptySquare;
10198 	board[toY][2] = BlackRook;
10199     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10200                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10201 	       && toY < promoRank && promoChar
10202                ) {
10203 	/* black pawn promotion */
10204 	board[toY][toX] = CharToPiece(ToLower(promoChar));
10205         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10206             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10207 	board[fromY][fromX] = EmptySquare;
10208     } else if ((fromY < BOARD_HEIGHT>>1)
10209 	       && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10210 	       && (toX != fromX)
10211                && gameInfo.variant != VariantXiangqi
10212                && gameInfo.variant != VariantBerolina
10213 	       && (pawn == BlackPawn)
10214 	       && (board[toY][toX] == EmptySquare)) {
10215 	board[fromY][fromX] = EmptySquare;
10216 	board[toY][toX] = piece;
10217 	if(toY == epRank - 128 - 1)
10218 	    captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10219 	else
10220 	    captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10221     } else if ((fromY == 3)
10222 	       && (toX == fromX)
10223                && gameInfo.variant == VariantBerolina
10224 	       && (board[fromY][fromX] == BlackPawn)
10225 	       && (board[toY][toX] == EmptySquare)) {
10226 	board[fromY][fromX] = EmptySquare;
10227 	board[toY][toX] = BlackPawn;
10228 	if(oldEP & EP_BEROLIN_A) {
10229 		captured = board[fromY][fromX-1];
10230 		board[fromY][fromX-1] = EmptySquare;
10231 	}else{	captured = board[fromY][fromX+1];
10232 		board[fromY][fromX+1] = EmptySquare;
10233 	}
10234     } else {
10235 	ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10236 	board[fromY][fromX] = EmptySquare;
10237 	board[toY][toX] = piece;
10238     }
10239   }
10240 
10241     if (gameInfo.holdingsWidth != 0) {
10242 
10243       /* !!A lot more code needs to be written to support holdings  */
10244       /* [HGM] OK, so I have written it. Holdings are stored in the */
10245       /* penultimate board files, so they are automaticlly stored   */
10246       /* in the game history.                                       */
10247       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10248                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10249         /* Delete from holdings, by decreasing count */
10250         /* and erasing image if necessary            */
10251         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10252         if(p < (int) BlackPawn) { /* white drop */
10253              p -= (int)WhitePawn;
10254 		 p = PieceToNumber((ChessSquare)p);
10255              if(p >= gameInfo.holdingsSize) p = 0;
10256              if(--board[p][BOARD_WIDTH-2] <= 0)
10257                   board[p][BOARD_WIDTH-1] = EmptySquare;
10258              if((int)board[p][BOARD_WIDTH-2] < 0)
10259 			board[p][BOARD_WIDTH-2] = 0;
10260         } else {                  /* black drop */
10261              p -= (int)BlackPawn;
10262 		 p = PieceToNumber((ChessSquare)p);
10263              if(p >= gameInfo.holdingsSize) p = 0;
10264              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10265                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10266              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10267 			board[BOARD_HEIGHT-1-p][1] = 0;
10268         }
10269       }
10270       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10271           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10272         /* [HGM] holdings: Add to holdings, if holdings exist */
10273 	if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10274 		// [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10275 		captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10276 	}
10277         p = (int) captured;
10278         if (p >= (int) BlackPawn) {
10279           p -= (int)BlackPawn;
10280           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10281                   /* Restore shogi-promoted piece to its original  first */
10282                   captured = (ChessSquare) (DEMOTED captured);
10283                   p = DEMOTED p;
10284           }
10285           p = PieceToNumber((ChessSquare)p);
10286           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10287           board[p][BOARD_WIDTH-2]++;
10288           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10289 	} else {
10290           p -= (int)WhitePawn;
10291           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10292                   captured = (ChessSquare) (DEMOTED captured);
10293                   p = DEMOTED p;
10294           }
10295           p = PieceToNumber((ChessSquare)p);
10296           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10297           board[BOARD_HEIGHT-1-p][1]++;
10298           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10299 	}
10300       }
10301     } else if (gameInfo.variant == VariantAtomic) {
10302       if (captured != EmptySquare) {
10303 	int y, x;
10304 	for (y = toY-1; y <= toY+1; y++) {
10305 	  for (x = toX-1; x <= toX+1; x++) {
10306             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10307 		board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10308 	      board[y][x] = EmptySquare;
10309 	    }
10310 	  }
10311 	}
10312 	board[toY][toX] = EmptySquare;
10313       }
10314     }
10315 
10316     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10317         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10318     } else
10319     if(promoChar == '+') {
10320         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10321         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10322         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10323           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10324     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10325         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10326 	if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10327 	   && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10328         board[toY][toX] = newPiece;
10329     }
10330     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10331 		&& promoChar != NULLCHAR && gameInfo.holdingsSize) {
10332 	// [HGM] superchess: take promotion piece out of holdings
10333 	int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10334 	if((int)piece < (int)BlackPawn) { // determine stm from piece color
10335 	    if(!--board[k][BOARD_WIDTH-2])
10336 		board[k][BOARD_WIDTH-1] = EmptySquare;
10337 	} else {
10338 	    if(!--board[BOARD_HEIGHT-1-k][1])
10339 		board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10340 	}
10341     }
10342 }
10343 
10344 /* Updates forwardMostMove */
10345 void
MakeMove(int fromX,int fromY,int toX,int toY,int promoChar)10346 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10347 {
10348     int x = toX, y = toY;
10349     char *s = parseList[forwardMostMove];
10350     ChessSquare p = boards[forwardMostMove][toY][toX];
10351 //    forwardMostMove++; // [HGM] bare: moved downstream
10352 
10353     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10354     (void) CoordsToAlgebraic(boards[forwardMostMove],
10355 			     PosFlags(forwardMostMove),
10356 			     fromY, fromX, y, x, promoChar,
10357 			     s);
10358     if(killX >= 0 && killY >= 0)
10359         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10360 
10361     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10362         int timeLeft; static int lastLoadFlag=0; int king, piece;
10363         piece = boards[forwardMostMove][fromY][fromX];
10364         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10365         if(gameInfo.variant == VariantKnightmate)
10366             king += (int) WhiteUnicorn - (int) WhiteKing;
10367         if(forwardMostMove == 0) {
10368             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10369                 fprintf(serverMoves, "%s;", UserName());
10370             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10371                 fprintf(serverMoves, "%s;", second.tidy);
10372             fprintf(serverMoves, "%s;", first.tidy);
10373             if(gameMode == MachinePlaysWhite)
10374                 fprintf(serverMoves, "%s;", UserName());
10375             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10376                 fprintf(serverMoves, "%s;", second.tidy);
10377         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10378         lastLoadFlag = loadFlag;
10379         // print base move
10380         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10381         // print castling suffix
10382         if( toY == fromY && piece == king ) {
10383             if(toX-fromX > 1)
10384                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10385             if(fromX-toX >1)
10386                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10387         }
10388         // e.p. suffix
10389         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10390              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10391              boards[forwardMostMove][toY][toX] == EmptySquare
10392              && fromX != toX && fromY != toY)
10393                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10394         // promotion suffix
10395         if(promoChar != NULLCHAR) {
10396             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10397                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10398 						 ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10399             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10400 	}
10401         if(!loadFlag) {
10402 		char buf[MOVE_LEN*2], *p; int len;
10403             fprintf(serverMoves, "/%d/%d",
10404                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10405             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10406             else                      timeLeft = blackTimeRemaining/1000;
10407             fprintf(serverMoves, "/%d", timeLeft);
10408 		strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10409 		if(p = strchr(buf, '/')) *p = NULLCHAR; else
10410 		if(p = strchr(buf, '=')) *p = NULLCHAR;
10411 		len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10412             fprintf(serverMoves, "/%s", buf);
10413         }
10414         fflush(serverMoves);
10415     }
10416 
10417     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10418 	GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10419       return;
10420     }
10421     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10422     if (commentList[forwardMostMove+1] != NULL) {
10423 	free(commentList[forwardMostMove+1]);
10424 	commentList[forwardMostMove+1] = NULL;
10425     }
10426     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10427     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10428     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10429     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10430     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10431     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10432     adjustedClock = FALSE;
10433     gameInfo.result = GameUnfinished;
10434     if (gameInfo.resultDetails != NULL) {
10435 	free(gameInfo.resultDetails);
10436 	gameInfo.resultDetails = NULL;
10437     }
10438     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10439 			      moveList[forwardMostMove - 1]);
10440     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10441       case MT_NONE:
10442       case MT_STALEMATE:
10443       default:
10444 	break;
10445       case MT_CHECK:
10446         if(!IS_SHOGI(gameInfo.variant))
10447             strcat(parseList[forwardMostMove - 1], "+");
10448 	break;
10449       case MT_CHECKMATE:
10450       case MT_STAINMATE:
10451 	strcat(parseList[forwardMostMove - 1], "#");
10452 	break;
10453     }
10454 }
10455 
10456 /* Updates currentMove if not pausing */
10457 void
ShowMove(int fromX,int fromY,int toX,int toY)10458 ShowMove (int fromX, int fromY, int toX, int toY)
10459 {
10460     int instant = (gameMode == PlayFromGameFile) ?
10461 	(matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10462     if(appData.noGUI) return;
10463     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10464 	if (!instant) {
10465 	    if (forwardMostMove == currentMove + 1) {
10466 		AnimateMove(boards[forwardMostMove - 1],
10467 			    fromX, fromY, toX, toY);
10468 	    }
10469 	}
10470 	currentMove = forwardMostMove;
10471     }
10472 
10473     killX = killY = -1; // [HGM] lion: used up
10474 
10475     if (instant) return;
10476 
10477     DisplayMove(currentMove - 1);
10478     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10479 	    if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10480 		SetHighlights(fromX, fromY, toX, toY);
10481 	    }
10482     }
10483     DrawPosition(FALSE, boards[currentMove]);
10484     DisplayBothClocks();
10485     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10486 }
10487 
10488 void
SendEgtPath(ChessProgramState * cps)10489 SendEgtPath (ChessProgramState *cps)
10490 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10491 	char buf[MSG_SIZ], name[MSG_SIZ], *p;
10492 
10493 	if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10494 
10495 	while(*p) {
10496 	    char c, *q = name+1, *r, *s;
10497 
10498 	    name[0] = ','; // extract next format name from feature and copy with prefixed ','
10499 	    while(*p && *p != ',') *q++ = *p++;
10500 	    *q++ = ':'; *q = 0;
10501 	    if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10502 		strcmp(name, ",nalimov:") == 0 ) {
10503 		// take nalimov path from the menu-changeable option first, if it is defined
10504 	      snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10505 		SendToProgram(buf,cps);     // send egtbpath command for nalimov
10506 	    } else
10507 	    if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10508 		(s = StrStr(appData.egtFormats, name)) != NULL) {
10509 		// format name occurs amongst user-supplied formats, at beginning or immediately after comma
10510 		s = r = StrStr(s, ":") + 1; // beginning of path info
10511 		while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10512 		c = *r; *r = 0;             // temporarily null-terminate path info
10513 		    *--q = 0;               // strip of trailig ':' from name
10514 		    snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10515 		*r = c;
10516 		SendToProgram(buf,cps);     // send egtbpath command for this format
10517 	    }
10518 	    if(*p == ',') p++; // read away comma to position for next format name
10519 	}
10520 }
10521 
10522 static int
NonStandardBoardSize(VariantClass v,int boardWidth,int boardHeight,int holdingsSize)10523 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10524 {
10525       int width = 8, height = 8, holdings = 0;             // most common sizes
10526       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10527       // correct the deviations default for each variant
10528       if( v == VariantXiangqi ) width = 9,  height = 10;
10529       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10530       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10531       if( v == VariantCapablanca || v == VariantCapaRandom ||
10532           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10533                                 width = 10;
10534       if( v == VariantCourier ) width = 12;
10535       if( v == VariantSuper )                            holdings = 8;
10536       if( v == VariantGreat )   width = 10,              holdings = 8;
10537       if( v == VariantSChess )                           holdings = 7;
10538       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10539       if( v == VariantChuChess) width = 10, height = 10;
10540       if( v == VariantChu )     width = 12, height = 12;
10541       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10542              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10543              holdingsSize >= 0 && holdingsSize != holdings;
10544 }
10545 
10546 char variantError[MSG_SIZ];
10547 
10548 char *
SupportedVariant(char * list,VariantClass v,int boardWidth,int boardHeight,int holdingsSize,int proto,char * engine)10549 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10550 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10551       char *p, *variant = VariantName(v);
10552       static char b[MSG_SIZ];
10553       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10554 	   snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10555                                                holdingsSize, variant); // cook up sized variant name
10556            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10557            if(StrStr(list, b) == NULL) {
10558                // specific sized variant not known, check if general sizing allowed
10559                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10560                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10561                             boardWidth, boardHeight, holdingsSize, engine);
10562                    return NULL;
10563                }
10564                /* [HGM] here we really should compare with the maximum supported board size */
10565            }
10566       } else snprintf(b, MSG_SIZ,"%s", variant);
10567       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10568       p = StrStr(list, b);
10569       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10570       if(p == NULL) {
10571           // occurs not at all in list, or only as sub-string
10572           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10573           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10574               int l = strlen(variantError);
10575               char *q;
10576               while(p != list && p[-1] != ',') p--;
10577               q = strchr(p, ',');
10578               if(q) *q = NULLCHAR;
10579               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10580               if(q) *q= ',';
10581           }
10582           return NULL;
10583       }
10584       return b;
10585 }
10586 
10587 void
InitChessProgram(ChessProgramState * cps,int setup)10588 InitChessProgram (ChessProgramState *cps, int setup)
10589 /* setup needed to setup FRC opening position */
10590 {
10591     char buf[MSG_SIZ], *b;
10592     if (appData.noChessProgram) return;
10593     hintRequested = FALSE;
10594     bookRequested = FALSE;
10595 
10596     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10597     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10598     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10599     if(cps->memSize) { /* [HGM] memory */
10600       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10601 	SendToProgram(buf, cps);
10602     }
10603     SendEgtPath(cps); /* [HGM] EGT */
10604     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10605       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10606 	SendToProgram(buf, cps);
10607     }
10608 
10609     setboardSpoiledMachineBlack = FALSE;
10610     SendToProgram(cps->initString, cps);
10611     if (gameInfo.variant != VariantNormal &&
10612 	gameInfo.variant != VariantLoadable
10613         /* [HGM] also send variant if board size non-standard */
10614         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10615 
10616       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10617                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10618       if (b == NULL) {
10619 	VariantClass v;
10620 	char c, *q = cps->variants, *p = strchr(q, ',');
10621 	if(p) *p = NULLCHAR;
10622 	v = StringToVariant(q);
10623 	DisplayError(variantError, 0);
10624 	if(v != VariantUnknown && cps == &first) {
10625 	    int w, h, s;
10626 	    if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10627 		appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10628 	    ASSIGN(appData.variant, q);
10629 	    Reset(TRUE, FALSE);
10630 	}
10631 	if(p) *p = ',';
10632 	return;
10633       }
10634 
10635       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10636       SendToProgram(buf, cps);
10637     }
10638     currentlyInitializedVariant = gameInfo.variant;
10639 
10640     /* [HGM] send opening position in FRC to first engine */
10641     if(setup) {
10642           SendToProgram("force\n", cps);
10643           SendBoard(cps, 0);
10644           /* engine is now in force mode! Set flag to wake it up after first move. */
10645           setboardSpoiledMachineBlack = 1;
10646     }
10647 
10648     if (cps->sendICS) {
10649       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10650       SendToProgram(buf, cps);
10651     }
10652     cps->maybeThinking = FALSE;
10653     cps->offeredDraw = 0;
10654     if (!appData.icsActive) {
10655 	SendTimeControl(cps, movesPerSession, timeControl,
10656 			timeIncrement, appData.searchDepth,
10657 			searchTime);
10658     }
10659     if (appData.showThinking
10660 	// [HGM] thinking: four options require thinking output to be sent
10661 	|| !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10662 				) {
10663 	SendToProgram("post\n", cps);
10664     }
10665     SendToProgram("hard\n", cps);
10666     if (!appData.ponderNextMove) {
10667 	/* Warning: "easy" is a toggle in GNU Chess, so don't send
10668 	   it without being sure what state we are in first.  "hard"
10669 	   is not a toggle, so that one is OK.
10670 	 */
10671 	SendToProgram("easy\n", cps);
10672     }
10673     if (cps->usePing) {
10674       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10675       SendToProgram(buf, cps);
10676     }
10677     cps->initDone = TRUE;
10678     ClearEngineOutputPane(cps == &second);
10679 }
10680 
10681 
10682 void
ResendOptions(ChessProgramState * cps)10683 ResendOptions (ChessProgramState *cps)
10684 { // send the stored value of the options
10685   int i;
10686   char buf[MSG_SIZ];
10687   Option *opt = cps->option;
10688   for(i=0; i<cps->nrOptions; i++, opt++) {
10689       switch(opt->type) {
10690         case Spin:
10691         case Slider:
10692         case CheckBox:
10693 	    snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10694           break;
10695         case ComboBox:
10696           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10697           break;
10698         default:
10699 	    snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10700           break;
10701         case Button:
10702         case SaveButton:
10703           continue;
10704       }
10705       SendToProgram(buf, cps);
10706   }
10707 }
10708 
10709 void
StartChessProgram(ChessProgramState * cps)10710 StartChessProgram (ChessProgramState *cps)
10711 {
10712     char buf[MSG_SIZ];
10713     int err;
10714 
10715     if (appData.noChessProgram) return;
10716     cps->initDone = FALSE;
10717 
10718     if (strcmp(cps->host, "localhost") == 0) {
10719 	err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10720     } else if (*appData.remoteShell == NULLCHAR) {
10721 	err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10722     } else {
10723 	if (*appData.remoteUser == NULLCHAR) {
10724 	  snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10725 		    cps->program);
10726 	} else {
10727 	  snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10728 		    cps->host, appData.remoteUser, cps->program);
10729 	}
10730 	err = StartChildProcess(buf, "", &cps->pr);
10731     }
10732 
10733     if (err != 0) {
10734       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10735 	DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10736 	if(cps != &first) return;
10737 	appData.noChessProgram = TRUE;
10738 	ThawUI();
10739 	SetNCPMode();
10740 //	DisplayFatalError(buf, err, 1);
10741 //	cps->pr = NoProc;
10742 //	cps->isr = NULL;
10743 	return;
10744     }
10745 
10746     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10747     if (cps->protocolVersion > 1) {
10748       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10749       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10750         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10751         cps->comboCnt = 0;  //                and values of combo boxes
10752       }
10753       SendToProgram(buf, cps);
10754       if(cps->reload) ResendOptions(cps);
10755     } else {
10756       SendToProgram("xboard\n", cps);
10757     }
10758 }
10759 
10760 void
TwoMachinesEventIfReady(void)10761 TwoMachinesEventIfReady P((void))
10762 {
10763   static int curMess = 0;
10764   if (first.lastPing != first.lastPong) {
10765     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10766     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10767     return;
10768   }
10769   if (second.lastPing != second.lastPong) {
10770     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10771     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10772     return;
10773   }
10774   DisplayMessage("", ""); curMess = 0;
10775   TwoMachinesEvent();
10776 }
10777 
10778 char *
MakeName(char * template)10779 MakeName (char *template)
10780 {
10781     time_t clock;
10782     struct tm *tm;
10783     static char buf[MSG_SIZ];
10784     char *p = buf;
10785     int i;
10786 
10787     clock = time((time_t *)NULL);
10788     tm = localtime(&clock);
10789 
10790     while(*p++ = *template++) if(p[-1] == '%') {
10791 	switch(*template++) {
10792 	  case 0:   *p = 0; return buf;
10793 	  case 'Y': i = tm->tm_year+1900; break;
10794 	  case 'y': i = tm->tm_year-100; break;
10795 	  case 'M': i = tm->tm_mon+1; break;
10796 	  case 'd': i = tm->tm_mday; break;
10797 	  case 'h': i = tm->tm_hour; break;
10798 	  case 'm': i = tm->tm_min; break;
10799 	  case 's': i = tm->tm_sec; break;
10800 	  default:  i = 0;
10801 	}
10802 	snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10803     }
10804     return buf;
10805 }
10806 
10807 int
CountPlayers(char * p)10808 CountPlayers (char *p)
10809 {
10810     int n = 0;
10811     while(p = strchr(p, '\n')) p++, n++; // count participants
10812     return n;
10813 }
10814 
10815 FILE *
WriteTourneyFile(char * results,FILE * f)10816 WriteTourneyFile (char *results, FILE *f)
10817 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10818     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10819     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10820 	// create a file with tournament description
10821 	fprintf(f, "-participants {%s}\n", appData.participants);
10822 	fprintf(f, "-seedBase %d\n", appData.seedBase);
10823 	fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10824 	fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10825 	fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10826 	fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10827 	fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10828 	fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10829 	fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10830 	fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10831 	fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10832 	fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10833 	fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10834 	fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10835 	fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10836 	fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10837 	fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10838 	fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10839 	fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10840 	fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10841 	fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10842 	fprintf(f, "-smpCores %d\n", appData.smpCores);
10843 	if(searchTime > 0)
10844 		fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10845 	else {
10846 		fprintf(f, "-mps %d\n", appData.movesPerSession);
10847 		fprintf(f, "-tc %s\n", appData.timeControl);
10848 		fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10849 	}
10850 	fprintf(f, "-results \"%s\"\n", results);
10851     }
10852     return f;
10853 }
10854 
10855 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10856 
10857 void
Substitute(char * participants,int expunge)10858 Substitute (char *participants, int expunge)
10859 {
10860     int i, changed, changes=0, nPlayers=0;
10861     char *p, *q, *r, buf[MSG_SIZ];
10862     if(participants == NULL) return;
10863     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10864     r = p = participants; q = appData.participants;
10865     while(*p && *p == *q) {
10866 	if(*p == '\n') r = p+1, nPlayers++;
10867 	p++; q++;
10868     }
10869     if(*p) { // difference
10870 	while(*p && *p++ != '\n');
10871 	while(*q && *q++ != '\n');
10872       changed = nPlayers;
10873 	changes = 1 + (strcmp(p, q) != 0);
10874     }
10875     if(changes == 1) { // a single engine mnemonic was changed
10876 	q = r; while(*q) nPlayers += (*q++ == '\n');
10877 	p = buf; while(*r && (*p = *r++) != '\n') p++;
10878 	*p = NULLCHAR;
10879 	NamesToList(firstChessProgramNames, command, mnemonic, "all");
10880 	for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10881 	if(mnemonic[i]) { // The substitute is valid
10882 	    FILE *f;
10883 	    if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10884 		flock(fileno(f), LOCK_EX);
10885 		ParseArgsFromFile(f);
10886 		fseek(f, 0, SEEK_SET);
10887 		FREE(appData.participants); appData.participants = participants;
10888 		if(expunge) { // erase results of replaced engine
10889 		    int len = strlen(appData.results), w, b, dummy;
10890 		    for(i=0; i<len; i++) {
10891 			Pairing(i, nPlayers, &w, &b, &dummy);
10892 			if((w == changed || b == changed) && appData.results[i] == '*') {
10893 			    DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10894 			    fclose(f);
10895 			    return;
10896 			}
10897 		    }
10898 		    for(i=0; i<len; i++) {
10899 			Pairing(i, nPlayers, &w, &b, &dummy);
10900 			if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10901 		    }
10902 		}
10903 		WriteTourneyFile(appData.results, f);
10904 		fclose(f); // release lock
10905 		return;
10906 	    }
10907 	} else DisplayError(_("No engine with the name you gave is installed"), 0);
10908     }
10909     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10910     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10911     free(participants);
10912     return;
10913 }
10914 
10915 int
CheckPlayers(char * participants)10916 CheckPlayers (char *participants)
10917 {
10918 	int i;
10919 	char buf[MSG_SIZ], *p;
10920 	NamesToList(firstChessProgramNames, command, mnemonic, "all");
10921 	while(p = strchr(participants, '\n')) {
10922 	    *p = NULLCHAR;
10923 	    for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10924 	    if(!mnemonic[i]) {
10925 		snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10926 		*p = '\n';
10927 		DisplayError(buf, 0);
10928 		return 1;
10929 	    }
10930 	    *p = '\n';
10931 	    participants = p + 1;
10932 	}
10933 	return 0;
10934 }
10935 
10936 int
CreateTourney(char * name)10937 CreateTourney (char *name)
10938 {
10939 	FILE *f;
10940 	if(matchMode && strcmp(name, appData.tourneyFile)) {
10941 	     ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10942 	}
10943 	if(name[0] == NULLCHAR) {
10944 	    if(appData.participants[0])
10945 		DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10946 	    return 0;
10947 	}
10948 	f = fopen(name, "r");
10949 	if(f) { // file exists
10950 	    ASSIGN(appData.tourneyFile, name);
10951 	    ParseArgsFromFile(f); // parse it
10952 	} else {
10953 	    if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10954 	    if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10955 		DisplayError(_("Not enough participants"), 0);
10956 		return 0;
10957 	    }
10958 	    if(CheckPlayers(appData.participants)) return 0;
10959 	    ASSIGN(appData.tourneyFile, name);
10960 	    if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10961 	    if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10962 	}
10963 	fclose(f);
10964 	appData.noChessProgram = FALSE;
10965 	appData.clockMode = TRUE;
10966 	SetGNUMode();
10967 	return 1;
10968 }
10969 
10970 int
NamesToList(char * names,char ** engineList,char ** engineMnemonic,char * group)10971 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10972 {
10973     char buf[MSG_SIZ], *p, *q;
10974     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10975     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10976     skip = !all && group[0]; // if group requested, we start in skip mode
10977     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10978 	p = names; q = buf; header = 0;
10979 	while(*p && *p != '\n') *q++ = *p++;
10980 	*q = 0;
10981 	if(*p == '\n') p++;
10982 	if(buf[0] == '#') {
10983 	    if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10984 	    depth++; // we must be entering a new group
10985 	    if(all) continue; // suppress printing group headers when complete list requested
10986 	    header = 1;
10987 	    if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10988 	}
10989 	if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10990 	if(engineList[i]) free(engineList[i]);
10991 	engineList[i] = strdup(buf);
10992 	if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10993 	if(engineMnemonic[i]) free(engineMnemonic[i]);
10994 	if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10995 	    strcat(buf, " (");
10996 	    sscanf(q + 8, "%s", buf + strlen(buf));
10997 	    strcat(buf, ")");
10998 	}
10999 	engineMnemonic[i] = strdup(buf);
11000 	i++;
11001     }
11002     engineList[i] = engineMnemonic[i] = NULL;
11003     return i;
11004 }
11005 
11006 // following implemented as macro to avoid type limitations
11007 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11008 
11009 void
SwapEngines(int n)11010 SwapEngines (int n)
11011 {   // swap settings for first engine and other engine (so far only some selected options)
11012     int h;
11013     char *p;
11014     if(n == 0) return;
11015     SWAP(directory, p)
11016     SWAP(chessProgram, p)
11017     SWAP(isUCI, h)
11018     SWAP(hasOwnBookUCI, h)
11019     SWAP(protocolVersion, h)
11020     SWAP(reuse, h)
11021     SWAP(scoreIsAbsolute, h)
11022     SWAP(timeOdds, h)
11023     SWAP(logo, p)
11024     SWAP(pgnName, p)
11025     SWAP(pvSAN, h)
11026     SWAP(engOptions, p)
11027     SWAP(engInitString, p)
11028     SWAP(computerString, p)
11029     SWAP(features, p)
11030     SWAP(fenOverride, p)
11031     SWAP(NPS, h)
11032     SWAP(accumulateTC, h)
11033     SWAP(drawDepth, h)
11034     SWAP(host, p)
11035     SWAP(pseudo, h)
11036 }
11037 
11038 int
GetEngineLine(char * s,int n)11039 GetEngineLine (char *s, int n)
11040 {
11041     int i;
11042     char buf[MSG_SIZ];
11043     extern char *icsNames;
11044     if(!s || !*s) return 0;
11045     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11046     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11047     if(!mnemonic[i]) return 0;
11048     if(n == 11) return 1; // just testing if there was a match
11049     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11050     if(n == 1) SwapEngines(n);
11051     ParseArgsFromString(buf);
11052     if(n == 1) SwapEngines(n);
11053     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11054 	SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11055 	ParseArgsFromString(buf);
11056     }
11057     return 1;
11058 }
11059 
11060 int
SetPlayer(int player,char * p)11061 SetPlayer (int player, char *p)
11062 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11063     int i;
11064     char buf[MSG_SIZ], *engineName;
11065     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11066     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11067     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11068     if(mnemonic[i]) {
11069 	snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11070 	ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11071 	appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11072 	ParseArgsFromString(buf);
11073     } else { // no engine with this nickname is installed!
11074 	snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11075 	ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11076 	matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11077 	ModeHighlight();
11078 	DisplayError(buf, 0);
11079 	return 0;
11080     }
11081     free(engineName);
11082     return i;
11083 }
11084 
11085 char *recentEngines;
11086 
11087 void
RecentEngineEvent(int nr)11088 RecentEngineEvent (int nr)
11089 {
11090     int n;
11091 //    SwapEngines(1); // bump first to second
11092 //    ReplaceEngine(&second, 1); // and load it there
11093     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11094     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11095     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11096 	ReplaceEngine(&first, 0);
11097 	FloatToFront(&appData.recentEngineList, command[n]);
11098     }
11099 }
11100 
11101 int
Pairing(int nr,int nPlayers,int * whitePlayer,int * blackPlayer,int * syncInterval)11102 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11103 {   // determine players from game number
11104     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11105 
11106     if(appData.tourneyType == 0) {
11107 	roundsPerCycle = (nPlayers - 1) | 1;
11108 	pairingsPerRound = nPlayers / 2;
11109     } else if(appData.tourneyType > 0) {
11110 	roundsPerCycle = nPlayers - appData.tourneyType;
11111 	pairingsPerRound = appData.tourneyType;
11112     }
11113     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11114     gamesPerCycle = gamesPerRound * roundsPerCycle;
11115     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11116     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11117     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11118     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11119     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11120     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11121 
11122     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11123     if(appData.roundSync) *syncInterval = gamesPerRound;
11124 
11125     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11126 
11127     if(appData.tourneyType == 0) {
11128 	if(curPairing == (nPlayers-1)/2 ) {
11129 	    *whitePlayer = curRound;
11130 	    *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11131 	} else {
11132 	    *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11133 	    if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11134 	    *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11135 	    if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11136 	}
11137     } else if(appData.tourneyType > 1) {
11138 	*blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11139 	*whitePlayer = curRound + appData.tourneyType;
11140     } else if(appData.tourneyType > 0) {
11141 	*whitePlayer = curPairing;
11142 	*blackPlayer = curRound + appData.tourneyType;
11143     }
11144 
11145     // take care of white/black alternation per round.
11146     // For cycles and games this is already taken care of by default, derived from matchGame!
11147     return curRound & 1;
11148 }
11149 
11150 int
NextTourneyGame(int nr,int * swapColors)11151 NextTourneyGame (int nr, int *swapColors)
11152 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11153     char *p, *q;
11154     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11155     FILE *tf;
11156     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11157     tf = fopen(appData.tourneyFile, "r");
11158     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11159     ParseArgsFromFile(tf); fclose(tf);
11160     InitTimeControls(); // TC might be altered from tourney file
11161 
11162     nPlayers = CountPlayers(appData.participants); // count participants
11163     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11164     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11165 
11166     if(syncInterval) {
11167 	p = q = appData.results;
11168 	while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11169 	if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11170 	    DisplayMessage(_("Waiting for other game(s)"),"");
11171 	    waitingForGame = TRUE;
11172 	    ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11173 	    return 0;
11174 	}
11175 	waitingForGame = FALSE;
11176     }
11177 
11178     if(appData.tourneyType < 0) {
11179 	if(nr>=0 && !pairingReceived) {
11180 	    char buf[1<<16];
11181 	    if(pairing.pr == NoProc) {
11182 		if(!appData.pairingEngine[0]) {
11183 		    DisplayFatalError(_("No pairing engine specified"), 0, 1);
11184 		    return 0;
11185 		}
11186 		StartChessProgram(&pairing); // starts the pairing engine
11187 	    }
11188 	    snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11189 	    SendToProgram(buf, &pairing);
11190 	    snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11191 	    SendToProgram(buf, &pairing);
11192 	    return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11193 	}
11194 	pairingReceived = 0;                              // ... so we continue here
11195 	*swapColors = 0;
11196 	appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11197 	whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11198 	matchGame = 1; roundNr = nr / syncInterval + 1;
11199     }
11200 
11201     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11202 
11203     // redefine engines, engine dir, etc.
11204     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11205     if(first.pr == NoProc) {
11206       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11207       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11208     }
11209     if(second.pr == NoProc) {
11210       SwapEngines(1);
11211       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11212       SwapEngines(1);         // and make that valid for second engine by swapping
11213       InitEngine(&second, 1);
11214     }
11215     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11216     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11217     return OK;
11218 }
11219 
11220 void
NextMatchGame()11221 NextMatchGame ()
11222 {   // performs game initialization that does not invoke engines, and then tries to start the game
11223     int res, firstWhite, swapColors = 0;
11224     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11225     if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game
11226 	char buf[MSG_SIZ];
11227 	snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11228 	if(strcmp(buf, currentDebugFile)) { // name has changed
11229 	    FILE *f = fopen(buf, "w");
11230 	    if(f) { // if opening the new file failed, just keep using the old one
11231 		ASSIGN(currentDebugFile, buf);
11232 		fclose(debugFP);
11233 		debugFP = f;
11234 	    }
11235 	    if(appData.serverFileName) {
11236 		if(serverFP) fclose(serverFP);
11237 		serverFP = fopen(appData.serverFileName, "w");
11238 		if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11239 		if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11240 	    }
11241 	}
11242     }
11243     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11244     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11245     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11246     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11247     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11248     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11249     Reset(FALSE, first.pr != NoProc);
11250     res = LoadGameOrPosition(matchGame); // setup game
11251     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11252     if(!res) return; // abort when bad game/pos file
11253     TwoMachinesEvent();
11254 }
11255 
11256 void
UserAdjudicationEvent(int result)11257 UserAdjudicationEvent (int result)
11258 {
11259     ChessMove gameResult = GameIsDrawn;
11260 
11261     if( result > 0 ) {
11262         gameResult = WhiteWins;
11263     }
11264     else if( result < 0 ) {
11265         gameResult = BlackWins;
11266     }
11267 
11268     if( gameMode == TwoMachinesPlay ) {
11269         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11270     }
11271 }
11272 
11273 
11274 // [HGM] save: calculate checksum of game to make games easily identifiable
11275 int
StringCheckSum(char * s)11276 StringCheckSum (char *s)
11277 {
11278 	int i = 0;
11279 	if(s==NULL) return 0;
11280 	while(*s) i = i*259 + *s++;
11281 	return i;
11282 }
11283 
11284 int
GameCheckSum()11285 GameCheckSum ()
11286 {
11287 	int i, sum=0;
11288 	for(i=backwardMostMove; i<forwardMostMove; i++) {
11289 		sum += pvInfoList[i].depth;
11290 		sum += StringCheckSum(parseList[i]);
11291 		sum += StringCheckSum(commentList[i]);
11292 		sum *= 261;
11293 	}
11294 	if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11295 	return sum + StringCheckSum(commentList[i]);
11296 } // end of save patch
11297 
11298 void
GameEnds(ChessMove result,char * resultDetails,int whosays)11299 GameEnds (ChessMove result, char *resultDetails, int whosays)
11300 {
11301     GameMode nextGameMode;
11302     int isIcsGame;
11303     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11304 
11305     if(endingGame) return; /* [HGM] crash: forbid recursion */
11306     endingGame = 1;
11307     if(twoBoards) { // [HGM] dual: switch back to one board
11308 	twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11309 	DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11310     }
11311     if (appData.debugMode) {
11312       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11313 	      result, resultDetails ? resultDetails : "(null)", whosays);
11314     }
11315 
11316     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11317 
11318     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11319 
11320     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11321 	/* If we are playing on ICS, the server decides when the
11322 	   game is over, but the engine can offer to draw, claim
11323 	   a draw, or resign.
11324 	 */
11325 #if ZIPPY
11326 	if (appData.zippyPlay && first.initDone) {
11327 	    if (result == GameIsDrawn) {
11328 		/* In case draw still needs to be claimed */
11329 		SendToICS(ics_prefix);
11330 		SendToICS("draw\n");
11331 	    } else if (StrCaseStr(resultDetails, "resign")) {
11332 		SendToICS(ics_prefix);
11333 		SendToICS("resign\n");
11334 	    }
11335         }
11336 #endif
11337 	endingGame = 0; /* [HGM] crash */
11338         return;
11339     }
11340 
11341     /* If we're loading the game from a file, stop */
11342     if (whosays == GE_FILE) {
11343       (void) StopLoadGameTimer();
11344       gameFileFP = NULL;
11345     }
11346 
11347     /* Cancel draw offers */
11348     first.offeredDraw = second.offeredDraw = 0;
11349 
11350     /* If this is an ICS game, only ICS can really say it's done;
11351        if not, anyone can. */
11352     isIcsGame = (gameMode == IcsPlayingWhite ||
11353 	         gameMode == IcsPlayingBlack ||
11354 		 gameMode == IcsObserving    ||
11355 		 gameMode == IcsExamining);
11356 
11357     if (!isIcsGame || whosays == GE_ICS) {
11358 	/* OK -- not an ICS game, or ICS said it was done */
11359 	StopClocks();
11360 	if (!isIcsGame && !appData.noChessProgram)
11361 	  SetUserThinkingEnables();
11362 
11363         /* [HGM] if a machine claims the game end we verify this claim */
11364         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11365 	    if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11366                 char claimer;
11367 		ChessMove trueResult = (ChessMove) -1;
11368 
11369                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11370                                             first.twoMachinesColor[0] :
11371                                             second.twoMachinesColor[0] ;
11372 
11373 		// [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11374 		if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11375 		    /* [HGM] verify: engine mate claims accepted if they were flagged */
11376 		    trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11377 		} else
11378 		if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11379 		    /* [HGM] verify: engine mate claims accepted if they were flagged */
11380 		    trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11381 		} else
11382 		if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11383 		    trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11384 		}
11385 
11386 		// now verify win claims, but not in drop games, as we don't understand those yet
11387                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11388 						 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11389                     (result == WhiteWins && claimer == 'w' ||
11390                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11391 		      if (appData.debugMode) {
11392 	 		fprintf(debugFP, "result=%d sp=%d move=%d\n",
11393 		 		result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11394 		      }
11395 		      if(result != trueResult) {
11396 			snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11397 	                      result = claimer == 'w' ? BlackWins : WhiteWins;
11398 	                      resultDetails = buf;
11399 		      }
11400                 } else
11401                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11402                     && (forwardMostMove <= backwardMostMove ||
11403                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11404                         (claimer=='b')==(forwardMostMove&1))
11405                                                                                   ) {
11406                       /* [HGM] verify: draws that were not flagged are false claims */
11407 		  snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11408                       result = claimer == 'w' ? BlackWins : WhiteWins;
11409                       resultDetails = buf;
11410                 }
11411                 /* (Claiming a loss is accepted no questions asked!) */
11412 	    } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11413 		forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11414 		result = GameUnfinished;
11415 		if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11416 	    }
11417 	    /* [HGM] bare: don't allow bare King to win */
11418 	    if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11419 					    || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11420 	       && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11421 	       && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11422 	       && result != GameIsDrawn)
11423 	    {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11424 		for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11425 			int p = (signed char)boards[forwardMostMove][i][j] - color;
11426 			if(p >= 0 && p <= (int)WhiteKing) k++;
11427 		}
11428 		if (appData.debugMode) {
11429 	 	     fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11430 		 	result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11431 		}
11432 		if(k <= 1) {
11433 			result = GameIsDrawn;
11434 			snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11435 			resultDetails = buf;
11436 		}
11437 	    }
11438         }
11439 
11440 
11441         if(serverMoves != NULL && !loadFlag) { char c = '=';
11442             if(result==WhiteWins) c = '+';
11443             if(result==BlackWins) c = '-';
11444             if(resultDetails != NULL)
11445                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11446         }
11447  	if (resultDetails != NULL) {
11448 	    gameInfo.result = result;
11449 	    gameInfo.resultDetails = StrSave(resultDetails);
11450 
11451 	    /* display last move only if game was not loaded from file */
11452 	    if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11453 		DisplayMove(currentMove - 1);
11454 
11455 	    if (forwardMostMove != 0) {
11456 		if (gameMode != PlayFromGameFile && gameMode != EditGame
11457 		    && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11458 								) {
11459 		    if (*appData.saveGameFile != NULLCHAR) {
11460 			if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11461 			    AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11462 			else
11463 			SaveGameToFile(appData.saveGameFile, TRUE);
11464 		    } else if (appData.autoSaveGames) {
11465 			if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11466 		    }
11467 		    if (*appData.savePositionFile != NULLCHAR) {
11468 			SavePositionToFile(appData.savePositionFile);
11469 		    }
11470 		    AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11471 		}
11472 	    }
11473 
11474 	    /* Tell program how game ended in case it is learning */
11475             /* [HGM] Moved this to after saving the PGN, just in case */
11476             /* engine died and we got here through time loss. In that */
11477             /* case we will get a fatal error writing the pipe, which */
11478             /* would otherwise lose us the PGN.                       */
11479             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11480             /* output during GameEnds should never be fatal anymore   */
11481 	    if (gameMode == MachinePlaysWhite ||
11482 		gameMode == MachinePlaysBlack ||
11483 		gameMode == TwoMachinesPlay ||
11484 		gameMode == IcsPlayingWhite ||
11485 		gameMode == IcsPlayingBlack ||
11486 		gameMode == BeginningOfGame) {
11487 		char buf[MSG_SIZ];
11488 		snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11489 			resultDetails);
11490 		if (first.pr != NoProc) {
11491 		    SendToProgram(buf, &first);
11492 		}
11493 		if (second.pr != NoProc &&
11494 		    gameMode == TwoMachinesPlay) {
11495 		    SendToProgram(buf, &second);
11496 		}
11497 	    }
11498 	}
11499 
11500 	if (appData.icsActive) {
11501 	    if (appData.quietPlay &&
11502 		(gameMode == IcsPlayingWhite ||
11503 		 gameMode == IcsPlayingBlack)) {
11504 		SendToICS(ics_prefix);
11505 		SendToICS("set shout 1\n");
11506 	    }
11507 	    nextGameMode = IcsIdle;
11508 	    ics_user_moved = FALSE;
11509 	    /* clean up premove.  It's ugly when the game has ended and the
11510 	     * premove highlights are still on the board.
11511 	     */
11512 	    if (gotPremove) {
11513 	      gotPremove = FALSE;
11514 	      ClearPremoveHighlights();
11515 	      DrawPosition(FALSE, boards[currentMove]);
11516 	    }
11517 	    if (whosays == GE_ICS) {
11518 		switch (result) {
11519 		case WhiteWins:
11520 		    if (gameMode == IcsPlayingWhite)
11521 			PlayIcsWinSound();
11522 		    else if(gameMode == IcsPlayingBlack)
11523 			PlayIcsLossSound();
11524 		    break;
11525 		case BlackWins:
11526 		    if (gameMode == IcsPlayingBlack)
11527 			PlayIcsWinSound();
11528 		    else if(gameMode == IcsPlayingWhite)
11529 			PlayIcsLossSound();
11530 		    break;
11531 		case GameIsDrawn:
11532 		    PlayIcsDrawSound();
11533 		    break;
11534 		default:
11535 		    PlayIcsUnfinishedSound();
11536 		}
11537 	    }
11538 	    if(appData.quitNext) { ExitEvent(0); return; }
11539 	} else if (gameMode == EditGame ||
11540 	           gameMode == PlayFromGameFile ||
11541 	           gameMode == AnalyzeMode ||
11542 		   gameMode == AnalyzeFile) {
11543 	    nextGameMode = gameMode;
11544 	} else {
11545 	    nextGameMode = EndOfGame;
11546 	}
11547 	pausing = FALSE;
11548 	ModeHighlight();
11549     } else {
11550 	nextGameMode = gameMode;
11551     }
11552 
11553     if (appData.noChessProgram) {
11554 	gameMode = nextGameMode;
11555 	ModeHighlight();
11556 	endingGame = 0; /* [HGM] crash */
11557         return;
11558     }
11559 
11560     if (first.reuse) {
11561 	/* Put first chess program into idle state */
11562 	if (first.pr != NoProc &&
11563 	    (gameMode == MachinePlaysWhite ||
11564 	     gameMode == MachinePlaysBlack ||
11565 	     gameMode == TwoMachinesPlay ||
11566 	     gameMode == IcsPlayingWhite ||
11567 	     gameMode == IcsPlayingBlack ||
11568 	     gameMode == BeginningOfGame)) {
11569 	    SendToProgram("force\n", &first);
11570 	    if (first.usePing) {
11571 	      char buf[MSG_SIZ];
11572 	      snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11573 	      SendToProgram(buf, &first);
11574 	    }
11575 	}
11576     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11577 	/* Kill off first chess program */
11578 	if (first.isr != NULL)
11579 	  RemoveInputSource(first.isr);
11580 	first.isr = NULL;
11581 
11582 	if (first.pr != NoProc) {
11583 	    ExitAnalyzeMode();
11584             DoSleep( appData.delayBeforeQuit );
11585 	    SendToProgram("quit\n", &first);
11586 	    DestroyChildProcess(first.pr, 4 + first.useSigterm);
11587 	    first.reload = TRUE;
11588 	}
11589 	first.pr = NoProc;
11590     }
11591     if (second.reuse) {
11592 	/* Put second chess program into idle state */
11593 	if (second.pr != NoProc &&
11594 	    gameMode == TwoMachinesPlay) {
11595 	    SendToProgram("force\n", &second);
11596 	    if (second.usePing) {
11597 	      char buf[MSG_SIZ];
11598 	      snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11599 	      SendToProgram(buf, &second);
11600 	    }
11601 	}
11602     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11603 	/* Kill off second chess program */
11604 	if (second.isr != NULL)
11605 	  RemoveInputSource(second.isr);
11606 	second.isr = NULL;
11607 
11608 	if (second.pr != NoProc) {
11609             DoSleep( appData.delayBeforeQuit );
11610 	    SendToProgram("quit\n", &second);
11611 	    DestroyChildProcess(second.pr, 4 + second.useSigterm);
11612 	    second.reload = TRUE;
11613 	}
11614 	second.pr = NoProc;
11615     }
11616 
11617     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11618 	char resChar = '=';
11619         switch (result) {
11620 	case WhiteWins:
11621 	  resChar = '+';
11622 	  if (first.twoMachinesColor[0] == 'w') {
11623 	    first.matchWins++;
11624 	  } else {
11625 	    second.matchWins++;
11626 	  }
11627 	  break;
11628 	case BlackWins:
11629 	  resChar = '-';
11630 	  if (first.twoMachinesColor[0] == 'b') {
11631 	    first.matchWins++;
11632 	  } else {
11633 	    second.matchWins++;
11634 	  }
11635 	  break;
11636 	case GameUnfinished:
11637 	  resChar = ' ';
11638 	default:
11639 	  break;
11640 	}
11641 
11642 	if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11643 	if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11644 	    if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11645 	    ReserveGame(nextGame, resChar); // sets nextGame
11646 	    if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11647 	    else ranking = strdup("busy"); //suppress popup when aborted but not finished
11648 	} else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11649 
11650 	if (nextGame <= appData.matchGames && !abortMatch) {
11651 	    gameMode = nextGameMode;
11652 	    matchGame = nextGame; // this will be overruled in tourney mode!
11653 	    GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11654             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11655 	    endingGame = 0; /* [HGM] crash */
11656 	    return;
11657 	} else {
11658 	    gameMode = nextGameMode;
11659 	    snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11660 		     first.tidy, second.tidy,
11661 		     first.matchWins, second.matchWins,
11662 		     appData.matchGames - (first.matchWins + second.matchWins));
11663 	    if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11664 	    if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11665 	    popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11666 	    if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11667 		first.twoMachinesColor = "black\n";
11668 		second.twoMachinesColor = "white\n";
11669 	    } else {
11670 		first.twoMachinesColor = "white\n";
11671 		second.twoMachinesColor = "black\n";
11672 	    }
11673 	}
11674     }
11675     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11676 	!(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11677       ExitAnalyzeMode();
11678     gameMode = nextGameMode;
11679     ModeHighlight();
11680     endingGame = 0;  /* [HGM] crash */
11681     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11682 	if(matchMode == TRUE) { // match through command line: exit with or without popup
11683 	    if(ranking) {
11684 		ToNrEvent(forwardMostMove);
11685 		if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11686 		else ExitEvent(0);
11687 	    } else DisplayFatalError(buf, 0, 0);
11688 	} else { // match through menu; just stop, with or without popup
11689 	    matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11690 	    ModeHighlight();
11691 	    if(ranking){
11692 		if(strcmp(ranking, "busy")) DisplayNote(ranking);
11693 	    } else DisplayNote(buf);
11694       }
11695       if(ranking) free(ranking);
11696     }
11697 }
11698 
11699 /* Assumes program was just initialized (initString sent).
11700    Leaves program in force mode. */
11701 void
FeedMovesToProgram(ChessProgramState * cps,int upto)11702 FeedMovesToProgram (ChessProgramState *cps, int upto)
11703 {
11704     int i;
11705 
11706     if (appData.debugMode)
11707       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11708 	      startedFromSetupPosition ? "position and " : "",
11709 	      backwardMostMove, upto, cps->which);
11710     if(currentlyInitializedVariant != gameInfo.variant) {
11711       char buf[MSG_SIZ];
11712         // [HGM] variantswitch: make engine aware of new variant
11713 	if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11714                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11715 		return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11716 	snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11717 	SendToProgram(buf, cps);
11718         currentlyInitializedVariant = gameInfo.variant;
11719     }
11720     SendToProgram("force\n", cps);
11721     if (startedFromSetupPosition) {
11722 	SendBoard(cps, backwardMostMove);
11723     if (appData.debugMode) {
11724         fprintf(debugFP, "feedMoves\n");
11725     }
11726     }
11727     for (i = backwardMostMove; i < upto; i++) {
11728 	SendMoveToProgram(i, cps);
11729     }
11730 }
11731 
11732 
11733 int
ResurrectChessProgram()11734 ResurrectChessProgram ()
11735 {
11736      /* The chess program may have exited.
11737 	If so, restart it and feed it all the moves made so far. */
11738     static int doInit = 0;
11739 
11740     if (appData.noChessProgram) return 1;
11741 
11742     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11743 	if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11744 	if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11745 	doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11746     } else {
11747 	if (first.pr != NoProc) return 1;
11748 	StartChessProgram(&first);
11749     }
11750     InitChessProgram(&first, FALSE);
11751     FeedMovesToProgram(&first, currentMove);
11752 
11753     if (!first.sendTime) {
11754 	/* can't tell gnuchess what its clock should read,
11755 	   so we bow to its notion. */
11756 	ResetClocks();
11757 	timeRemaining[0][currentMove] = whiteTimeRemaining;
11758 	timeRemaining[1][currentMove] = blackTimeRemaining;
11759     }
11760 
11761     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11762                 appData.icsEngineAnalyze) && first.analysisSupport) {
11763       SendToProgram("analyze\n", &first);
11764       first.analyzing = TRUE;
11765     }
11766     return 1;
11767 }
11768 
11769 /*
11770  * Button procedures
11771  */
11772 void
Reset(int redraw,int init)11773 Reset (int redraw, int init)
11774 {
11775     int i;
11776 
11777     if (appData.debugMode) {
11778 	fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11779 		redraw, init, gameMode);
11780     }
11781     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11782     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11783     CleanupTail(); // [HGM] vari: delete any stored variations
11784     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11785     pausing = pauseExamInvalid = FALSE;
11786     startedFromSetupPosition = blackPlaysFirst = FALSE;
11787     firstMove = TRUE;
11788     whiteFlag = blackFlag = FALSE;
11789     userOfferedDraw = FALSE;
11790     hintRequested = bookRequested = FALSE;
11791     first.maybeThinking = FALSE;
11792     second.maybeThinking = FALSE;
11793     first.bookSuspend = FALSE; // [HGM] book
11794     second.bookSuspend = FALSE;
11795     thinkOutput[0] = NULLCHAR;
11796     lastHint[0] = NULLCHAR;
11797     ClearGameInfo(&gameInfo);
11798     gameInfo.variant = StringToVariant(appData.variant);
11799     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11800     ics_user_moved = ics_clock_paused = FALSE;
11801     ics_getting_history = H_FALSE;
11802     ics_gamenum = -1;
11803     white_holding[0] = black_holding[0] = NULLCHAR;
11804     ClearProgramStats();
11805     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11806 
11807     ResetFrontEnd();
11808     ClearHighlights();
11809     flipView = appData.flipView;
11810     ClearPremoveHighlights();
11811     gotPremove = FALSE;
11812     alarmSounded = FALSE;
11813     killX = killY = -1; // [HGM] lion
11814 
11815     GameEnds(EndOfFile, NULL, GE_PLAYER);
11816     if(appData.serverMovesName != NULL) {
11817         /* [HGM] prepare to make moves file for broadcasting */
11818         clock_t t = clock();
11819         if(serverMoves != NULL) fclose(serverMoves);
11820         serverMoves = fopen(appData.serverMovesName, "r");
11821         if(serverMoves != NULL) {
11822             fclose(serverMoves);
11823             /* delay 15 sec before overwriting, so all clients can see end */
11824             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11825         }
11826         serverMoves = fopen(appData.serverMovesName, "w");
11827     }
11828 
11829     ExitAnalyzeMode();
11830     gameMode = BeginningOfGame;
11831     ModeHighlight();
11832     if(appData.icsActive) gameInfo.variant = VariantNormal;
11833     currentMove = forwardMostMove = backwardMostMove = 0;
11834     MarkTargetSquares(1);
11835     InitPosition(redraw);
11836     for (i = 0; i < MAX_MOVES; i++) {
11837 	if (commentList[i] != NULL) {
11838 	    free(commentList[i]);
11839 	    commentList[i] = NULL;
11840 	}
11841     }
11842     ResetClocks();
11843     timeRemaining[0][0] = whiteTimeRemaining;
11844     timeRemaining[1][0] = blackTimeRemaining;
11845 
11846     if (first.pr == NoProc) {
11847 	StartChessProgram(&first);
11848     }
11849     if (init) {
11850 	    InitChessProgram(&first, startedFromSetupPosition);
11851     }
11852     DisplayTitle("");
11853     DisplayMessage("", "");
11854     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11855     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11856     ClearMap();        // [HGM] exclude: invalidate map
11857 }
11858 
11859 void
AutoPlayGameLoop()11860 AutoPlayGameLoop ()
11861 {
11862     for (;;) {
11863 	if (!AutoPlayOneMove())
11864 	  return;
11865 	if (matchMode || appData.timeDelay == 0)
11866 	  continue;
11867 	if (appData.timeDelay < 0)
11868 	  return;
11869 	StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11870 	break;
11871     }
11872 }
11873 
11874 void
AnalyzeNextGame()11875 AnalyzeNextGame()
11876 {
11877     ReloadGame(1); // next game
11878 }
11879 
11880 int
AutoPlayOneMove()11881 AutoPlayOneMove ()
11882 {
11883     int fromX, fromY, toX, toY;
11884 
11885     if (appData.debugMode) {
11886       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11887     }
11888 
11889     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11890       return FALSE;
11891 
11892     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11893       pvInfoList[currentMove].depth = programStats.depth;
11894       pvInfoList[currentMove].score = programStats.score;
11895       pvInfoList[currentMove].time  = 0;
11896       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11897       else { // append analysis of final position as comment
11898 	char buf[MSG_SIZ];
11899 	snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11900 	AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11901       }
11902       programStats.depth = 0;
11903     }
11904 
11905     if (currentMove >= forwardMostMove) {
11906       if(gameMode == AnalyzeFile) {
11907 	  if(appData.loadGameIndex == -1) {
11908 	    GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11909           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11910 	  } else {
11911           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11912         }
11913       }
11914 //      gameMode = EndOfGame;
11915 //      ModeHighlight();
11916 
11917       /* [AS] Clear current move marker at the end of a game */
11918       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11919 
11920       return FALSE;
11921     }
11922 
11923     toX = moveList[currentMove][2] - AAA;
11924     toY = moveList[currentMove][3] - ONE;
11925 
11926     if (moveList[currentMove][1] == '@') {
11927 	if (appData.highlightLastMove) {
11928 	    SetHighlights(-1, -1, toX, toY);
11929 	}
11930     } else {
11931         int viaX = moveList[currentMove][5] - AAA;
11932         int viaY = moveList[currentMove][6] - ONE;
11933         fromX = moveList[currentMove][0] - AAA;
11934         fromY = moveList[currentMove][1] - ONE;
11935 
11936         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11937 
11938         if(moveList[currentMove][4] == ';') { // multi-leg
11939             ChessSquare piece = boards[currentMove][viaY][viaX];
11940 	    AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11941             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11942             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11943             boards[currentMove][viaY][viaX] = piece;
11944         } else
11945 	AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11946 
11947 	if (appData.highlightLastMove) {
11948 	    SetHighlights(fromX, fromY, toX, toY);
11949 	}
11950     }
11951     DisplayMove(currentMove);
11952     SendMoveToProgram(currentMove++, &first);
11953     DisplayBothClocks();
11954     DrawPosition(FALSE, boards[currentMove]);
11955     // [HGM] PV info: always display, routine tests if empty
11956     DisplayComment(currentMove - 1, commentList[currentMove]);
11957     return TRUE;
11958 }
11959 
11960 
11961 int
LoadGameOneMove(ChessMove readAhead)11962 LoadGameOneMove (ChessMove readAhead)
11963 {
11964     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11965     char promoChar = NULLCHAR;
11966     ChessMove moveType;
11967     char move[MSG_SIZ];
11968     char *p, *q;
11969 
11970     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11971 	gameMode != AnalyzeMode && gameMode != Training) {
11972 	gameFileFP = NULL;
11973 	return FALSE;
11974     }
11975 
11976     yyboardindex = forwardMostMove;
11977     if (readAhead != EndOfFile) {
11978       moveType = readAhead;
11979     } else {
11980       if (gameFileFP == NULL)
11981 	  return FALSE;
11982       moveType = (ChessMove) Myylex();
11983     }
11984 
11985     done = FALSE;
11986     switch (moveType) {
11987       case Comment:
11988 	if (appData.debugMode)
11989 	  fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11990 	p = yy_text;
11991 
11992 	/* append the comment but don't display it */
11993 	AppendComment(currentMove, p, FALSE);
11994 	return TRUE;
11995 
11996       case WhiteCapturesEnPassant:
11997       case BlackCapturesEnPassant:
11998       case WhitePromotion:
11999       case BlackPromotion:
12000       case WhiteNonPromotion:
12001       case BlackNonPromotion:
12002       case NormalMove:
12003       case FirstLeg:
12004       case WhiteKingSideCastle:
12005       case WhiteQueenSideCastle:
12006       case BlackKingSideCastle:
12007       case BlackQueenSideCastle:
12008       case WhiteKingSideCastleWild:
12009       case WhiteQueenSideCastleWild:
12010       case BlackKingSideCastleWild:
12011       case BlackQueenSideCastleWild:
12012       /* PUSH Fabien */
12013       case WhiteHSideCastleFR:
12014       case WhiteASideCastleFR:
12015       case BlackHSideCastleFR:
12016       case BlackASideCastleFR:
12017       /* POP Fabien */
12018 	if (appData.debugMode)
12019 	  fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12020         fromX = currentMoveString[0] - AAA;
12021         fromY = currentMoveString[1] - ONE;
12022         toX = currentMoveString[2] - AAA;
12023         toY = currentMoveString[3] - ONE;
12024 	promoChar = currentMoveString[4];
12025 	if(promoChar == ';') promoChar = NULLCHAR;
12026 	break;
12027 
12028       case WhiteDrop:
12029       case BlackDrop:
12030 	if (appData.debugMode)
12031 	  fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12032 	fromX = moveType == WhiteDrop ?
12033 	  (int) CharToPiece(ToUpper(currentMoveString[0])) :
12034 	(int) CharToPiece(ToLower(currentMoveString[0]));
12035 	fromY = DROP_RANK;
12036         toX = currentMoveString[2] - AAA;
12037         toY = currentMoveString[3] - ONE;
12038 	break;
12039 
12040       case WhiteWins:
12041       case BlackWins:
12042       case GameIsDrawn:
12043       case GameUnfinished:
12044 	if (appData.debugMode)
12045 	  fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12046 	p = strchr(yy_text, '{');
12047 	if (p == NULL) p = strchr(yy_text, '(');
12048 	if (p == NULL) {
12049 	    p = yy_text;
12050 	    if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12051 	} else {
12052 	    q = strchr(p, *p == '{' ? '}' : ')');
12053 	    if (q != NULL) *q = NULLCHAR;
12054 	    p++;
12055 	}
12056 	while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12057 	GameEnds(moveType, p, GE_FILE);
12058 	done = TRUE;
12059 	if (cmailMsgLoaded) {
12060  	    ClearHighlights();
12061 	    flipView = WhiteOnMove(currentMove);
12062 	    if (moveType == GameUnfinished) flipView = !flipView;
12063 	    if (appData.debugMode)
12064 	      fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12065 	}
12066 	break;
12067 
12068       case EndOfFile:
12069 	if (appData.debugMode)
12070 	  fprintf(debugFP, "Parser hit end of file\n");
12071 	switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12072 	  case MT_NONE:
12073 	  case MT_CHECK:
12074 	    break;
12075 	  case MT_CHECKMATE:
12076 	  case MT_STAINMATE:
12077 	    if (WhiteOnMove(currentMove)) {
12078 		GameEnds(BlackWins, "Black mates", GE_FILE);
12079 	    } else {
12080 		GameEnds(WhiteWins, "White mates", GE_FILE);
12081 	    }
12082 	    break;
12083 	  case MT_STALEMATE:
12084 	    GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12085 	    break;
12086 	}
12087 	done = TRUE;
12088 	break;
12089 
12090       case MoveNumberOne:
12091 	if (lastLoadGameStart == GNUChessGame) {
12092 	    /* GNUChessGames have numbers, but they aren't move numbers */
12093 	    if (appData.debugMode)
12094 	      fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12095 		      yy_text, (int) moveType);
12096 	    return LoadGameOneMove(EndOfFile); /* tail recursion */
12097 	}
12098 	/* else fall thru */
12099 
12100       case XBoardGame:
12101       case GNUChessGame:
12102       case PGNTag:
12103 	/* Reached start of next game in file */
12104 	if (appData.debugMode)
12105 	  fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12106 	switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12107 	  case MT_NONE:
12108 	  case MT_CHECK:
12109 	    break;
12110 	  case MT_CHECKMATE:
12111 	  case MT_STAINMATE:
12112 	    if (WhiteOnMove(currentMove)) {
12113 		GameEnds(BlackWins, "Black mates", GE_FILE);
12114 	    } else {
12115 		GameEnds(WhiteWins, "White mates", GE_FILE);
12116 	    }
12117 	    break;
12118 	  case MT_STALEMATE:
12119 	    GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12120 	    break;
12121 	}
12122 	done = TRUE;
12123 	break;
12124 
12125       case PositionDiagram:	/* should not happen; ignore */
12126       case ElapsedTime:		/* ignore */
12127       case NAG:                 /* ignore */
12128 	if (appData.debugMode)
12129 	  fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12130 		  yy_text, (int) moveType);
12131 	return LoadGameOneMove(EndOfFile); /* tail recursion */
12132 
12133       case IllegalMove:
12134 	if (appData.testLegality) {
12135 	    if (appData.debugMode)
12136 	      fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12137 	    snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12138 		    (forwardMostMove / 2) + 1,
12139 		    WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12140 	    DisplayError(move, 0);
12141 	    done = TRUE;
12142 	} else {
12143 	    if (appData.debugMode)
12144 	      fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12145 		      yy_text, currentMoveString);
12146             if(currentMoveString[1] == '@') {
12147                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12148                 fromY = DROP_RANK;
12149             } else {
12150                 fromX = currentMoveString[0] - AAA;
12151                 fromY = currentMoveString[1] - ONE;
12152             }
12153             toX = currentMoveString[2] - AAA;
12154             toY = currentMoveString[3] - ONE;
12155 	    promoChar = currentMoveString[4];
12156 	}
12157 	break;
12158 
12159       case AmbiguousMove:
12160 	if (appData.debugMode)
12161 	  fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12162 	snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12163 		(forwardMostMove / 2) + 1,
12164 		WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12165 	DisplayError(move, 0);
12166 	done = TRUE;
12167 	break;
12168 
12169       default:
12170       case ImpossibleMove:
12171 	if (appData.debugMode)
12172 	  fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12173 	snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12174 		(forwardMostMove / 2) + 1,
12175 		WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12176 	DisplayError(move, 0);
12177 	done = TRUE;
12178 	break;
12179     }
12180 
12181     if (done) {
12182 	if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12183 	    DrawPosition(FALSE, boards[currentMove]);
12184 	    DisplayBothClocks();
12185             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12186 	      DisplayComment(currentMove - 1, commentList[currentMove]);
12187 	}
12188 	(void) StopLoadGameTimer();
12189 	gameFileFP = NULL;
12190 	cmailOldMove = forwardMostMove;
12191 	return FALSE;
12192     } else {
12193 	/* currentMoveString is set as a side-effect of yylex */
12194 
12195 	thinkOutput[0] = NULLCHAR;
12196 	MakeMove(fromX, fromY, toX, toY, promoChar);
12197 	killX = killY = -1; // [HGM] lion: used up
12198 	currentMove = forwardMostMove;
12199 	return TRUE;
12200     }
12201 }
12202 
12203 /* Load the nth game from the given file */
12204 int
LoadGameFromFile(char * filename,int n,char * title,int useList)12205 LoadGameFromFile (char *filename, int n, char *title, int useList)
12206 {
12207     FILE *f;
12208     char buf[MSG_SIZ];
12209 
12210     if (strcmp(filename, "-") == 0) {
12211 	f = stdin;
12212 	title = "stdin";
12213     } else {
12214 	f = fopen(filename, "rb");
12215 	if (f == NULL) {
12216 	  snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12217 	    DisplayError(buf, errno);
12218 	    return FALSE;
12219 	}
12220     }
12221     if (fseek(f, 0, 0) == -1) {
12222 	/* f is not seekable; probably a pipe */
12223 	useList = FALSE;
12224     }
12225     if (useList && n == 0) {
12226 	int error = GameListBuild(f);
12227 	if (error) {
12228 	    DisplayError(_("Cannot build game list"), error);
12229 	} else if (!ListEmpty(&gameList) &&
12230 		   ((ListGame *) gameList.tailPred)->number > 1) {
12231 	    GameListPopUp(f, title);
12232 	    return TRUE;
12233 	}
12234 	GameListDestroy();
12235 	n = 1;
12236     }
12237     if (n == 0) n = 1;
12238     return LoadGame(f, n, title, FALSE);
12239 }
12240 
12241 
12242 void
MakeRegisteredMove()12243 MakeRegisteredMove ()
12244 {
12245     int fromX, fromY, toX, toY;
12246     char promoChar;
12247     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12248 	switch (cmailMoveType[lastLoadGameNumber - 1]) {
12249 	  case CMAIL_MOVE:
12250 	  case CMAIL_DRAW:
12251 	    if (appData.debugMode)
12252 	      fprintf(debugFP, "Restoring %s for game %d\n",
12253 		      cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12254 
12255 	    thinkOutput[0] = NULLCHAR;
12256 	    safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12257             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12258             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12259             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12260             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12261 	    promoChar = cmailMove[lastLoadGameNumber - 1][4];
12262 	    MakeMove(fromX, fromY, toX, toY, promoChar);
12263 	    ShowMove(fromX, fromY, toX, toY);
12264 
12265 	    switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12266 	      case MT_NONE:
12267 	      case MT_CHECK:
12268 		break;
12269 
12270 	      case MT_CHECKMATE:
12271 	      case MT_STAINMATE:
12272 		if (WhiteOnMove(currentMove)) {
12273 		    GameEnds(BlackWins, "Black mates", GE_PLAYER);
12274 		} else {
12275 		    GameEnds(WhiteWins, "White mates", GE_PLAYER);
12276 		}
12277 		break;
12278 
12279 	      case MT_STALEMATE:
12280 		GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12281 		break;
12282 	    }
12283 
12284 	    break;
12285 
12286 	  case CMAIL_RESIGN:
12287 	    if (WhiteOnMove(currentMove)) {
12288 		GameEnds(BlackWins, "White resigns", GE_PLAYER);
12289 	    } else {
12290 		GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12291 	    }
12292 	    break;
12293 
12294 	  case CMAIL_ACCEPT:
12295 	    GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12296 	    break;
12297 
12298 	  default:
12299 	    break;
12300 	}
12301     }
12302 
12303     return;
12304 }
12305 
12306 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12307 int
CmailLoadGame(FILE * f,int gameNumber,char * title,int useList)12308 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12309 {
12310     int retVal;
12311 
12312     if (gameNumber > nCmailGames) {
12313 	DisplayError(_("No more games in this message"), 0);
12314 	return FALSE;
12315     }
12316     if (f == lastLoadGameFP) {
12317 	int offset = gameNumber - lastLoadGameNumber;
12318 	if (offset == 0) {
12319 	    cmailMsg[0] = NULLCHAR;
12320 	    if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12321 		cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12322 		nCmailMovesRegistered--;
12323 	    }
12324 	    cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12325 	    if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12326 		cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12327 	    }
12328 	} else {
12329 	    if (! RegisterMove()) return FALSE;
12330 	}
12331     }
12332 
12333     retVal = LoadGame(f, gameNumber, title, useList);
12334 
12335     /* Make move registered during previous look at this game, if any */
12336     MakeRegisteredMove();
12337 
12338     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12339 	commentList[currentMove]
12340 	  = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12341 	DisplayComment(currentMove - 1, commentList[currentMove]);
12342     }
12343 
12344     return retVal;
12345 }
12346 
12347 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12348 int
ReloadGame(int offset)12349 ReloadGame (int offset)
12350 {
12351     int gameNumber = lastLoadGameNumber + offset;
12352     if (lastLoadGameFP == NULL) {
12353 	DisplayError(_("No game has been loaded yet"), 0);
12354 	return FALSE;
12355     }
12356     if (gameNumber <= 0) {
12357 	DisplayError(_("Can't back up any further"), 0);
12358 	return FALSE;
12359     }
12360     if (cmailMsgLoaded) {
12361 	return CmailLoadGame(lastLoadGameFP, gameNumber,
12362 			     lastLoadGameTitle, lastLoadGameUseList);
12363     } else {
12364 	return LoadGame(lastLoadGameFP, gameNumber,
12365 			lastLoadGameTitle, lastLoadGameUseList);
12366     }
12367 }
12368 
12369 int keys[EmptySquare+1];
12370 
12371 int
PositionMatches(Board b1,Board b2)12372 PositionMatches (Board b1, Board b2)
12373 {
12374     int r, f, sum=0;
12375     switch(appData.searchMode) {
12376 	case 1: return CompareWithRights(b1, b2);
12377 	case 2:
12378 	    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12379 		if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12380 	    }
12381 	    return TRUE;
12382 	case 3:
12383 	    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12384 	      if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12385 		sum += keys[b1[r][f]] - keys[b2[r][f]];
12386 	    }
12387 	    return sum==0;
12388 	case 4:
12389 	    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12390 		sum += keys[b1[r][f]] - keys[b2[r][f]];
12391 	    }
12392 	    return sum==0;
12393     }
12394     return TRUE;
12395 }
12396 
12397 #define Q_PROMO  4
12398 #define Q_EP     3
12399 #define Q_BCASTL 2
12400 #define Q_WCASTL 1
12401 
12402 int pieceList[256], quickBoard[256];
12403 ChessSquare pieceType[256] = { EmptySquare };
12404 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12405 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12406 int soughtTotal, turn;
12407 Boolean epOK, flipSearch;
12408 
12409 typedef struct {
12410     unsigned char piece, to;
12411 } Move;
12412 
12413 #define DSIZE (250000)
12414 
12415 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12416 Move *moveDatabase = initialSpace;
12417 unsigned int movePtr, dataSize = DSIZE;
12418 
12419 int
MakePieceList(Board board,int * counts)12420 MakePieceList (Board board, int *counts)
12421 {
12422     int r, f, n=Q_PROMO, total=0;
12423     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12424     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12425 	int sq = f + (r<<4);
12426         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12427 	    quickBoard[sq] = ++n;
12428 	    pieceList[n] = sq;
12429 	    pieceType[n] = board[r][f];
12430 	    counts[board[r][f]]++;
12431 	    if(board[r][f] == WhiteKing) pieceList[1] = n; else
12432 	    if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12433 	    total++;
12434 	}
12435     }
12436     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12437     return total;
12438 }
12439 
12440 void
PackMove(int fromX,int fromY,int toX,int toY,ChessSquare promoPiece)12441 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12442 {
12443     int sq = fromX + (fromY<<4);
12444     int piece = quickBoard[sq], rook;
12445     quickBoard[sq] = 0;
12446     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12447     if(piece == pieceList[1] && fromY == toY) {
12448       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12449 	int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12450 	moveDatabase[movePtr++].piece = Q_WCASTL;
12451 	quickBoard[sq] = piece;
12452 	piece = quickBoard[from]; quickBoard[from] = 0;
12453 	moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12454       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12455 	quickBoard[sq] = 0; // remove Rook
12456 	moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12457 	moveDatabase[movePtr++].piece = Q_WCASTL;
12458 	quickBoard[sq] = pieceList[1]; // put King
12459 	piece = rook;
12460 	moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12461       }
12462     } else
12463     if(piece == pieceList[2] && fromY == toY) {
12464       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12465 	int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12466 	moveDatabase[movePtr++].piece = Q_BCASTL;
12467 	quickBoard[sq] = piece;
12468 	piece = quickBoard[from]; quickBoard[from] = 0;
12469 	moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12470       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12471 	quickBoard[sq] = 0; // remove Rook
12472 	moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12473 	moveDatabase[movePtr++].piece = Q_BCASTL;
12474 	quickBoard[sq] = pieceList[2]; // put King
12475 	piece = rook;
12476 	moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12477       }
12478     } else
12479     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12480 	quickBoard[(fromY<<4)+toX] = 0;
12481 	moveDatabase[movePtr].piece = Q_EP;
12482 	moveDatabase[movePtr++].to = (fromY<<4)+toX;
12483 	moveDatabase[movePtr].to = sq;
12484     } else
12485     if(promoPiece != pieceType[piece]) {
12486 	moveDatabase[movePtr++].piece = Q_PROMO;
12487 	moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12488     }
12489     moveDatabase[movePtr].piece = piece;
12490     quickBoard[sq] = piece;
12491     movePtr++;
12492 }
12493 
12494 int
PackGame(Board board)12495 PackGame (Board board)
12496 {
12497     Move *newSpace = NULL;
12498     moveDatabase[movePtr].piece = 0; // terminate previous game
12499     if(movePtr > dataSize) {
12500 	if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12501 	dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12502 	if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12503 	if(newSpace) {
12504 	    int i;
12505 	    Move *p = moveDatabase, *q = newSpace;
12506 	    for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12507 	    if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12508 	    moveDatabase = newSpace;
12509 	} else { // calloc failed, we must be out of memory. Too bad...
12510 	    dataSize = 0; // prevent calloc events for all subsequent games
12511 	    return 0;     // and signal this one isn't cached
12512 	}
12513     }
12514     movePtr++;
12515     MakePieceList(board, counts);
12516     return movePtr;
12517 }
12518 
12519 int
QuickCompare(Board board,int * minCounts,int * maxCounts)12520 QuickCompare (Board board, int *minCounts, int *maxCounts)
12521 {   // compare according to search mode
12522     int r, f;
12523     switch(appData.searchMode)
12524     {
12525       case 1: // exact position match
12526 	if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12527 	for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12528 	    if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12529 	}
12530 	break;
12531       case 2: // can have extra material on empty squares
12532 	for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12533 	    if(board[r][f] == EmptySquare) continue;
12534 	    if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12535 	}
12536 	break;
12537       case 3: // material with exact Pawn structure
12538 	for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12539 	    if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12540 	    if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12541 	} // fall through to material comparison
12542       case 4: // exact material
12543 	for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12544 	break;
12545       case 6: // material range with given imbalance
12546 	for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12547 	// fall through to range comparison
12548       case 5: // material range
12549 	for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12550     }
12551     return TRUE;
12552 }
12553 
12554 int
QuickScan(Board board,Move * move)12555 QuickScan (Board board, Move *move)
12556 {   // reconstruct game,and compare all positions in it
12557     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12558     do {
12559 	int piece = move->piece;
12560 	int to = move->to, from = pieceList[piece];
12561 	if(found < 0) { // if already found just scan to game end for final piece count
12562 	  if(QuickCompare(soughtBoard, minSought, maxSought) ||
12563 	   appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12564 	   flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12565 				appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12566 	    ) {
12567 	    static int lastCounts[EmptySquare+1];
12568 	    int i;
12569 	    if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12570 	    if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12571 	  } else stretch = 0;
12572 	  if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12573 	  if(found >= 0 && !appData.minPieces) return found;
12574 	}
12575 	if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12576 	  if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12577 	  if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12578 	    piece = (++move)->piece;
12579 	    from = pieceList[piece];
12580 	    counts[pieceType[piece]]--;
12581 	    pieceType[piece] = (ChessSquare) move->to;
12582 	    counts[move->to]++;
12583 	  } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12584 	    counts[pieceType[quickBoard[to]]]--;
12585 	    quickBoard[to] = 0; total--;
12586 	    move++;
12587 	    continue;
12588 	  } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12589 	    piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12590 	    from  = pieceList[piece]; // so this must be King
12591 	    quickBoard[from] = 0;
12592 	    pieceList[piece] = to;
12593 	    from = pieceList[(++move)->piece]; // for FRC this has to be done here
12594 	    quickBoard[from] = 0; // rook
12595 	    quickBoard[to] = piece;
12596 	    to = move->to; piece = move->piece;
12597 	    goto aftercastle;
12598 	  }
12599 	}
12600 	if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12601 	if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12602 	quickBoard[from] = 0;
12603       aftercastle:
12604 	quickBoard[to] = piece;
12605 	pieceList[piece] = to;
12606 	cnt++; turn ^= 3;
12607 	move++;
12608     } while(1);
12609 }
12610 
12611 void
InitSearch()12612 InitSearch ()
12613 {
12614     int r, f;
12615     flipSearch = FALSE;
12616     CopyBoard(soughtBoard, boards[currentMove]);
12617     soughtTotal = MakePieceList(soughtBoard, maxSought);
12618     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12619     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12620     CopyBoard(reverseBoard, boards[currentMove]);
12621     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12622 	int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12623 	if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12624 	reverseBoard[r][f] = piece;
12625     }
12626     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12627     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12628     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12629 		 || (boards[currentMove][CASTLING][2] == NoRights ||
12630 		     boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12631 		 && (boards[currentMove][CASTLING][5] == NoRights ||
12632 		     boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12633       ) {
12634 	flipSearch = TRUE;
12635 	CopyBoard(flipBoard, soughtBoard);
12636 	CopyBoard(rotateBoard, reverseBoard);
12637 	for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12638 	    flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12639 	    rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12640 	}
12641     }
12642     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12643     if(appData.searchMode >= 5) {
12644 	for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12645 	MakePieceList(soughtBoard, minSought);
12646 	for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12647     }
12648     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12649 	soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12650 }
12651 
12652 GameInfo dummyInfo;
12653 static int creatingBook;
12654 
12655 int
GameContainsPosition(FILE * f,ListGame * lg)12656 GameContainsPosition (FILE *f, ListGame *lg)
12657 {
12658     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12659     int fromX, fromY, toX, toY;
12660     char promoChar;
12661     static int initDone=FALSE;
12662 
12663     // weed out games based on numerical tag comparison
12664     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12665     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12666     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12667     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12668     if(!initDone) {
12669 	for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12670 	initDone = TRUE;
12671     }
12672     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12673     else CopyBoard(boards[scratch], initialPosition); // default start position
12674     if(lg->moves) {
12675 	turn = btm + 1;
12676 	if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12677 	if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12678     }
12679     if(btm) plyNr++;
12680     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12681     fseek(f, lg->offset, 0);
12682     yynewfile(f);
12683     while(1) {
12684 	yyboardindex = scratch;
12685 	quickFlag = plyNr+1;
12686 	next = Myylex();
12687 	quickFlag = 0;
12688 	switch(next) {
12689 	    case PGNTag:
12690 		if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12691 	    default:
12692 		continue;
12693 
12694 	    case XBoardGame:
12695 	    case GNUChessGame:
12696 		if(plyNr) return -1; // after we have seen moves, this is for new game
12697 	      continue;
12698 
12699 	    case AmbiguousMove: // we cannot reconstruct the game beyond these two
12700 	    case ImpossibleMove:
12701 	    case WhiteWins: // game ends here with these four
12702 	    case BlackWins:
12703 	    case GameIsDrawn:
12704 	    case GameUnfinished:
12705 		return -1;
12706 
12707 	    case IllegalMove:
12708 		if(appData.testLegality) return -1;
12709 	    case WhiteCapturesEnPassant:
12710 	    case BlackCapturesEnPassant:
12711 	    case WhitePromotion:
12712 	    case BlackPromotion:
12713 	    case WhiteNonPromotion:
12714 	    case BlackNonPromotion:
12715 	    case NormalMove:
12716 	    case FirstLeg:
12717 	    case WhiteKingSideCastle:
12718 	    case WhiteQueenSideCastle:
12719 	    case BlackKingSideCastle:
12720 	    case BlackQueenSideCastle:
12721 	    case WhiteKingSideCastleWild:
12722 	    case WhiteQueenSideCastleWild:
12723 	    case BlackKingSideCastleWild:
12724 	    case BlackQueenSideCastleWild:
12725 	    case WhiteHSideCastleFR:
12726 	    case WhiteASideCastleFR:
12727 	    case BlackHSideCastleFR:
12728 	    case BlackASideCastleFR:
12729 		fromX = currentMoveString[0] - AAA;
12730 		fromY = currentMoveString[1] - ONE;
12731 		toX = currentMoveString[2] - AAA;
12732 		toY = currentMoveString[3] - ONE;
12733 		promoChar = currentMoveString[4];
12734 		break;
12735 	    case WhiteDrop:
12736 	    case BlackDrop:
12737 		fromX = next == WhiteDrop ?
12738 		  (int) CharToPiece(ToUpper(currentMoveString[0])) :
12739 		  (int) CharToPiece(ToLower(currentMoveString[0]));
12740 		fromY = DROP_RANK;
12741 		toX = currentMoveString[2] - AAA;
12742 		toY = currentMoveString[3] - ONE;
12743 		promoChar = 0;
12744 		break;
12745 	}
12746 	// Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12747 	plyNr++;
12748 	ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12749 	if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12750 	if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12751 	if(appData.findMirror) {
12752 	    if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12753 	    if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12754 	}
12755     }
12756 }
12757 
12758 /* Load the nth game from open file f */
12759 int
LoadGame(FILE * f,int gameNumber,char * title,int useList)12760 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12761 {
12762     ChessMove cm;
12763     char buf[MSG_SIZ];
12764     int gn = gameNumber;
12765     ListGame *lg = NULL;
12766     int numPGNTags = 0;
12767     int err, pos = -1;
12768     GameMode oldGameMode;
12769     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12770     char oldName[MSG_SIZ];
12771 
12772     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12773 
12774     if (appData.debugMode)
12775 	fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12776 
12777     if (gameMode == Training )
12778 	SetTrainingModeOff();
12779 
12780     oldGameMode = gameMode;
12781     if (gameMode != BeginningOfGame) {
12782       Reset(FALSE, TRUE);
12783     }
12784     killX = killY = -1; // [HGM] lion: in case we did not Reset
12785 
12786     gameFileFP = f;
12787     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12788 	fclose(lastLoadGameFP);
12789     }
12790 
12791     if (useList) {
12792 	lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12793 
12794 	if (lg) {
12795 	    fseek(f, lg->offset, 0);
12796 	    GameListHighlight(gameNumber);
12797 	    pos = lg->position;
12798 	    gn = 1;
12799 	}
12800 	else {
12801 	    if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12802 	      appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12803 	    else
12804 	    DisplayError(_("Game number out of range"), 0);
12805 	    return FALSE;
12806 	}
12807     } else {
12808 	GameListDestroy();
12809 	if (fseek(f, 0, 0) == -1) {
12810 	    if (f == lastLoadGameFP ?
12811 	 	gameNumber == lastLoadGameNumber + 1 :
12812 		gameNumber == 1) {
12813 		gn = 1;
12814 	    } else {
12815 		DisplayError(_("Can't seek on game file"), 0);
12816 		return FALSE;
12817 	    }
12818 	}
12819     }
12820     lastLoadGameFP = f;
12821     lastLoadGameNumber = gameNumber;
12822     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12823     lastLoadGameUseList = useList;
12824 
12825     yynewfile(f);
12826 
12827     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12828       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12829 		lg->gameInfo.black);
12830 	    DisplayTitle(buf);
12831     } else if (*title != NULLCHAR) {
12832 	if (gameNumber > 1) {
12833 	  snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12834 	    DisplayTitle(buf);
12835 	} else {
12836 	    DisplayTitle(title);
12837 	}
12838     }
12839 
12840     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12841 	gameMode = PlayFromGameFile;
12842 	ModeHighlight();
12843     }
12844 
12845     currentMove = forwardMostMove = backwardMostMove = 0;
12846     CopyBoard(boards[0], initialPosition);
12847     StopClocks();
12848 
12849     /*
12850      * Skip the first gn-1 games in the file.
12851      * Also skip over anything that precedes an identifiable
12852      * start of game marker, to avoid being confused by
12853      * garbage at the start of the file.  Currently
12854      * recognized start of game markers are the move number "1",
12855      * the pattern "gnuchess .* game", the pattern
12856      * "^[#;%] [^ ]* game file", and a PGN tag block.
12857      * A game that starts with one of the latter two patterns
12858      * will also have a move number 1, possibly
12859      * following a position diagram.
12860      * 5-4-02: Let's try being more lenient and allowing a game to
12861      * start with an unnumbered move.  Does that break anything?
12862      */
12863     cm = lastLoadGameStart = EndOfFile;
12864     while (gn > 0) {
12865 	yyboardindex = forwardMostMove;
12866 	cm = (ChessMove) Myylex();
12867 	switch (cm) {
12868 	  case EndOfFile:
12869 	    if (cmailMsgLoaded) {
12870 		nCmailGames = CMAIL_MAX_GAMES - gn;
12871 	    } else {
12872 		Reset(TRUE, TRUE);
12873 		DisplayError(_("Game not found in file"), 0);
12874 	    }
12875 	    return FALSE;
12876 
12877 	  case GNUChessGame:
12878 	  case XBoardGame:
12879 	    gn--;
12880 	    lastLoadGameStart = cm;
12881 	    break;
12882 
12883 	  case MoveNumberOne:
12884 	    switch (lastLoadGameStart) {
12885 	      case GNUChessGame:
12886 	      case XBoardGame:
12887 	      case PGNTag:
12888 		break;
12889 	      case MoveNumberOne:
12890 	      case EndOfFile:
12891 		gn--;		/* count this game */
12892 		lastLoadGameStart = cm;
12893 		break;
12894 	      default:
12895 		/* impossible */
12896 		break;
12897 	    }
12898 	    break;
12899 
12900 	  case PGNTag:
12901 	    switch (lastLoadGameStart) {
12902 	      case GNUChessGame:
12903 	      case PGNTag:
12904 	      case MoveNumberOne:
12905 	      case EndOfFile:
12906 		gn--;		/* count this game */
12907 		lastLoadGameStart = cm;
12908 		break;
12909 	      case XBoardGame:
12910 		lastLoadGameStart = cm; /* game counted already */
12911 		break;
12912 	      default:
12913 		/* impossible */
12914 		break;
12915 	    }
12916 	    if (gn > 0) {
12917 		do {
12918 		    yyboardindex = forwardMostMove;
12919 		    cm = (ChessMove) Myylex();
12920 		} while (cm == PGNTag || cm == Comment);
12921 	    }
12922 	    break;
12923 
12924 	  case WhiteWins:
12925 	  case BlackWins:
12926 	  case GameIsDrawn:
12927 	    if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12928 		if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12929 		    != CMAIL_OLD_RESULT) {
12930 		    nCmailResults ++ ;
12931 		    cmailResult[  CMAIL_MAX_GAMES
12932 				- gn - 1] = CMAIL_OLD_RESULT;
12933 		}
12934 	    }
12935 	    break;
12936 
12937 	  case NormalMove:
12938 	  case FirstLeg:
12939 	    /* Only a NormalMove can be at the start of a game
12940 	     * without a position diagram. */
12941 	    if (lastLoadGameStart == EndOfFile ) {
12942 	      gn--;
12943 	      lastLoadGameStart = MoveNumberOne;
12944 	    }
12945 	    break;
12946 
12947 	  default:
12948 	    break;
12949 	}
12950     }
12951 
12952     if (appData.debugMode)
12953       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12954 
12955     if (cm == XBoardGame) {
12956 	/* Skip any header junk before position diagram and/or move 1 */
12957 	for (;;) {
12958 	    yyboardindex = forwardMostMove;
12959 	    cm = (ChessMove) Myylex();
12960 
12961 	    if (cm == EndOfFile ||
12962 		cm == GNUChessGame || cm == XBoardGame) {
12963 		/* Empty game; pretend end-of-file and handle later */
12964 		cm = EndOfFile;
12965 		break;
12966 	    }
12967 
12968 	    if (cm == MoveNumberOne || cm == PositionDiagram ||
12969 		cm == PGNTag || cm == Comment)
12970 	      break;
12971 	}
12972     } else if (cm == GNUChessGame) {
12973 	if (gameInfo.event != NULL) {
12974 	    free(gameInfo.event);
12975 	}
12976 	gameInfo.event = StrSave(yy_text);
12977     }
12978 
12979     startedFromSetupPosition = FALSE;
12980     while (cm == PGNTag) {
12981 	if (appData.debugMode)
12982 	  fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12983 	err = ParsePGNTag(yy_text, &gameInfo);
12984 	if (!err) numPGNTags++;
12985 
12986         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12987         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12988             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12989 	    ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12990 	    InitPosition(TRUE);
12991             oldVariant = gameInfo.variant;
12992 	    if (appData.debugMode)
12993 	      fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12994         }
12995 
12996 
12997 	if (gameInfo.fen != NULL) {
12998 	  Board initial_position;
12999 	  startedFromSetupPosition = TRUE;
13000 	  if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13001 	    Reset(TRUE, TRUE);
13002 	    DisplayError(_("Bad FEN position in file"), 0);
13003 	    return FALSE;
13004 	  }
13005 	  CopyBoard(boards[0], initial_position);
13006 	  if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13007 	    CopyBoard(initialPosition, initial_position);
13008 	  if (blackPlaysFirst) {
13009 	    currentMove = forwardMostMove = backwardMostMove = 1;
13010 	    CopyBoard(boards[1], initial_position);
13011 	    safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13012 	    safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13013 	    timeRemaining[0][1] = whiteTimeRemaining;
13014 	    timeRemaining[1][1] = blackTimeRemaining;
13015 	    if (commentList[0] != NULL) {
13016 	      commentList[1] = commentList[0];
13017 	      commentList[0] = NULL;
13018 	    }
13019 	  } else {
13020 	    currentMove = forwardMostMove = backwardMostMove = 0;
13021 	  }
13022           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13023           {   int i;
13024               initialRulePlies = FENrulePlies;
13025               for( i=0; i< nrCastlingRights; i++ )
13026                   initialRights[i] = initial_position[CASTLING][i];
13027           }
13028 	  yyboardindex = forwardMostMove;
13029 	  free(gameInfo.fen);
13030 	  gameInfo.fen = NULL;
13031 	}
13032 
13033 	yyboardindex = forwardMostMove;
13034 	cm = (ChessMove) Myylex();
13035 
13036 	/* Handle comments interspersed among the tags */
13037 	while (cm == Comment) {
13038 	    char *p;
13039 	    if (appData.debugMode)
13040 	      fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13041 	    p = yy_text;
13042 	    AppendComment(currentMove, p, FALSE);
13043 	    yyboardindex = forwardMostMove;
13044 	    cm = (ChessMove) Myylex();
13045 	}
13046     }
13047 
13048     /* don't rely on existence of Event tag since if game was
13049      * pasted from clipboard the Event tag may not exist
13050      */
13051     if (numPGNTags > 0){
13052         char *tags;
13053 	if (gameInfo.variant == VariantNormal) {
13054 	  VariantClass v = StringToVariant(gameInfo.event);
13055 	  // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13056 	  if(v < VariantShogi) gameInfo.variant = v;
13057 	}
13058 	if (!matchMode) {
13059           if( appData.autoDisplayTags ) {
13060 	    tags = PGNTags(&gameInfo);
13061 	    TagsPopUp(tags, CmailMsg());
13062 	    free(tags);
13063           }
13064 	}
13065     } else {
13066 	/* Make something up, but don't display it now */
13067 	SetGameInfo();
13068 	TagsPopDown();
13069     }
13070 
13071     if (cm == PositionDiagram) {
13072 	int i, j;
13073 	char *p;
13074 	Board initial_position;
13075 
13076 	if (appData.debugMode)
13077 	  fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13078 
13079 	if (!startedFromSetupPosition) {
13080 	    p = yy_text;
13081             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13082               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13083 		switch (*p) {
13084 		  case '{':
13085 		  case '[':
13086 		  case '-':
13087 		  case ' ':
13088 		  case '\t':
13089 		  case '\n':
13090 		  case '\r':
13091 		    break;
13092 		  default:
13093 		    initial_position[i][j++] = CharToPiece(*p);
13094 		    break;
13095 		}
13096 	    while (*p == ' ' || *p == '\t' ||
13097 		   *p == '\n' || *p == '\r') p++;
13098 
13099 	    if (strncmp(p, "black", strlen("black"))==0)
13100 	      blackPlaysFirst = TRUE;
13101 	    else
13102 	      blackPlaysFirst = FALSE;
13103 	    startedFromSetupPosition = TRUE;
13104 
13105 	    CopyBoard(boards[0], initial_position);
13106 	    if (blackPlaysFirst) {
13107 		currentMove = forwardMostMove = backwardMostMove = 1;
13108 		CopyBoard(boards[1], initial_position);
13109 		safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13110 		safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13111 		timeRemaining[0][1] = whiteTimeRemaining;
13112 		timeRemaining[1][1] = blackTimeRemaining;
13113 		if (commentList[0] != NULL) {
13114 		    commentList[1] = commentList[0];
13115 		    commentList[0] = NULL;
13116 		}
13117 	    } else {
13118 		currentMove = forwardMostMove = backwardMostMove = 0;
13119 	    }
13120 	}
13121 	yyboardindex = forwardMostMove;
13122 	cm = (ChessMove) Myylex();
13123     }
13124 
13125   if(!creatingBook) {
13126     if (first.pr == NoProc) {
13127 	StartChessProgram(&first);
13128     }
13129     InitChessProgram(&first, FALSE);
13130     if(gameInfo.variant == VariantUnknown && *oldName) {
13131 	safeStrCpy(engineVariant, oldName, MSG_SIZ);
13132 	gameInfo.variant = v;
13133     }
13134     SendToProgram("force\n", &first);
13135     if (startedFromSetupPosition) {
13136 	SendBoard(&first, forwardMostMove);
13137     if (appData.debugMode) {
13138         fprintf(debugFP, "Load Game\n");
13139     }
13140 	DisplayBothClocks();
13141     }
13142   }
13143 
13144     /* [HGM] server: flag to write setup moves in broadcast file as one */
13145     loadFlag = appData.suppressLoadMoves;
13146 
13147     while (cm == Comment) {
13148 	char *p;
13149 	if (appData.debugMode)
13150 	  fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13151 	p = yy_text;
13152 	AppendComment(currentMove, p, FALSE);
13153 	yyboardindex = forwardMostMove;
13154 	cm = (ChessMove) Myylex();
13155     }
13156 
13157     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13158 	cm == WhiteWins || cm == BlackWins ||
13159 	cm == GameIsDrawn || cm == GameUnfinished) {
13160 	DisplayMessage("", _("No moves in game"));
13161 	if (cmailMsgLoaded) {
13162 	    if (appData.debugMode)
13163 	      fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13164  	    ClearHighlights();
13165 	    flipView = FALSE;
13166 	}
13167 	DrawPosition(FALSE, boards[currentMove]);
13168 	DisplayBothClocks();
13169 	gameMode = EditGame;
13170 	ModeHighlight();
13171 	gameFileFP = NULL;
13172 	cmailOldMove = 0;
13173 	return TRUE;
13174     }
13175 
13176     // [HGM] PV info: routine tests if comment empty
13177     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13178 	DisplayComment(currentMove - 1, commentList[currentMove]);
13179     }
13180     if (!matchMode && appData.timeDelay != 0)
13181       DrawPosition(FALSE, boards[currentMove]);
13182 
13183     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13184       programStats.ok_to_send = 1;
13185     }
13186 
13187     /* if the first token after the PGN tags is a move
13188      * and not move number 1, retrieve it from the parser
13189      */
13190     if (cm != MoveNumberOne)
13191 	LoadGameOneMove(cm);
13192 
13193     /* load the remaining moves from the file */
13194     while (LoadGameOneMove(EndOfFile)) {
13195       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13196       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13197     }
13198 
13199     /* rewind to the start of the game */
13200     currentMove = backwardMostMove;
13201 
13202     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13203 
13204     if (oldGameMode == AnalyzeFile) {
13205       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13206       AnalyzeFileEvent();
13207     } else
13208     if (oldGameMode == AnalyzeMode) {
13209       AnalyzeFileEvent();
13210     }
13211 
13212     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13213 	long int w, b; // [HGM] adjourn: restore saved clock times
13214 	char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13215 	if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13216 	    timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13217 	    timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13218 	}
13219     }
13220 
13221     if(creatingBook) return TRUE;
13222     if (!matchMode && pos > 0) {
13223 	ToNrEvent(pos); // [HGM] no autoplay if selected on position
13224     } else
13225     if (matchMode || appData.timeDelay == 0) {
13226       ToEndEvent();
13227     } else if (appData.timeDelay > 0) {
13228       AutoPlayGameLoop();
13229     }
13230 
13231     if (appData.debugMode)
13232 	fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13233 
13234     loadFlag = 0; /* [HGM] true game starts */
13235     return TRUE;
13236 }
13237 
13238 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13239 int
ReloadPosition(int offset)13240 ReloadPosition (int offset)
13241 {
13242     int positionNumber = lastLoadPositionNumber + offset;
13243     if (lastLoadPositionFP == NULL) {
13244 	DisplayError(_("No position has been loaded yet"), 0);
13245 	return FALSE;
13246     }
13247     if (positionNumber <= 0) {
13248 	DisplayError(_("Can't back up any further"), 0);
13249 	return FALSE;
13250     }
13251     return LoadPosition(lastLoadPositionFP, positionNumber,
13252 			lastLoadPositionTitle);
13253 }
13254 
13255 /* Load the nth position from the given file */
13256 int
LoadPositionFromFile(char * filename,int n,char * title)13257 LoadPositionFromFile (char *filename, int n, char *title)
13258 {
13259     FILE *f;
13260     char buf[MSG_SIZ];
13261 
13262     if (strcmp(filename, "-") == 0) {
13263 	return LoadPosition(stdin, n, "stdin");
13264     } else {
13265 	f = fopen(filename, "rb");
13266 	if (f == NULL) {
13267             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13268 	    DisplayError(buf, errno);
13269 	    return FALSE;
13270 	} else {
13271 	    return LoadPosition(f, n, title);
13272 	}
13273     }
13274 }
13275 
13276 /* Load the nth position from the given open file, and close it */
13277 int
LoadPosition(FILE * f,int positionNumber,char * title)13278 LoadPosition (FILE *f, int positionNumber, char *title)
13279 {
13280     char *p, line[MSG_SIZ];
13281     Board initial_position;
13282     int i, j, fenMode, pn;
13283 
13284     if (gameMode == Training )
13285 	SetTrainingModeOff();
13286 
13287     if (gameMode != BeginningOfGame) {
13288 	Reset(FALSE, TRUE);
13289     }
13290     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13291 	fclose(lastLoadPositionFP);
13292     }
13293     if (positionNumber == 0) positionNumber = 1;
13294     lastLoadPositionFP = f;
13295     lastLoadPositionNumber = positionNumber;
13296     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13297     if (first.pr == NoProc && !appData.noChessProgram) {
13298       StartChessProgram(&first);
13299       InitChessProgram(&first, FALSE);
13300     }
13301     pn = positionNumber;
13302     if (positionNumber < 0) {
13303 	/* Negative position number means to seek to that byte offset */
13304 	if (fseek(f, -positionNumber, 0) == -1) {
13305 	    DisplayError(_("Can't seek on position file"), 0);
13306 	    return FALSE;
13307 	};
13308 	pn = 1;
13309     } else {
13310 	if (fseek(f, 0, 0) == -1) {
13311 	    if (f == lastLoadPositionFP ?
13312 		positionNumber == lastLoadPositionNumber + 1 :
13313 		positionNumber == 1) {
13314 		pn = 1;
13315 	    } else {
13316 		DisplayError(_("Can't seek on position file"), 0);
13317 		return FALSE;
13318 	    }
13319 	}
13320     }
13321     /* See if this file is FEN or old-style xboard */
13322     if (fgets(line, MSG_SIZ, f) == NULL) {
13323 	DisplayError(_("Position not found in file"), 0);
13324 	return FALSE;
13325     }
13326     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13327     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13328 
13329     if (pn >= 2) {
13330 	if (fenMode || line[0] == '#') pn--;
13331 	while (pn > 0) {
13332 	    /* skip positions before number pn */
13333 	    if (fgets(line, MSG_SIZ, f) == NULL) {
13334 	        Reset(TRUE, TRUE);
13335 		DisplayError(_("Position not found in file"), 0);
13336 		return FALSE;
13337 	    }
13338 	    if (fenMode || line[0] == '#') pn--;
13339 	}
13340     }
13341 
13342     if (fenMode) {
13343 	char *p;
13344 	if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13345 	    DisplayError(_("Bad FEN position in file"), 0);
13346 	    return FALSE;
13347 	}
13348 	if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13349 	    sscanf(p+3, "%s", bestMove);
13350 	} else *bestMove = NULLCHAR;
13351     } else {
13352 	(void) fgets(line, MSG_SIZ, f);
13353 	(void) fgets(line, MSG_SIZ, f);
13354 
13355         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13356 	    (void) fgets(line, MSG_SIZ, f);
13357             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13358 		if (*p == ' ')
13359 		  continue;
13360 		initial_position[i][j++] = CharToPiece(*p);
13361 	    }
13362 	}
13363 
13364 	blackPlaysFirst = FALSE;
13365 	if (!feof(f)) {
13366 	    (void) fgets(line, MSG_SIZ, f);
13367 	    if (strncmp(line, "black", strlen("black"))==0)
13368 	      blackPlaysFirst = TRUE;
13369 	}
13370     }
13371     startedFromSetupPosition = TRUE;
13372 
13373     CopyBoard(boards[0], initial_position);
13374     if (blackPlaysFirst) {
13375 	currentMove = forwardMostMove = backwardMostMove = 1;
13376 	safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13377 	safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13378 	CopyBoard(boards[1], initial_position);
13379 	DisplayMessage("", _("Black to play"));
13380     } else {
13381 	currentMove = forwardMostMove = backwardMostMove = 0;
13382 	DisplayMessage("", _("White to play"));
13383     }
13384     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13385     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13386 	SendToProgram("force\n", &first);
13387 	SendBoard(&first, forwardMostMove);
13388     }
13389     if (appData.debugMode) {
13390 int i, j;
13391   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13392   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13393         fprintf(debugFP, "Load Position\n");
13394     }
13395 
13396     if (positionNumber > 1) {
13397       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13398 	DisplayTitle(line);
13399     } else {
13400 	DisplayTitle(title);
13401     }
13402     gameMode = EditGame;
13403     ModeHighlight();
13404     ResetClocks();
13405     timeRemaining[0][1] = whiteTimeRemaining;
13406     timeRemaining[1][1] = blackTimeRemaining;
13407     DrawPosition(FALSE, boards[currentMove]);
13408 
13409     return TRUE;
13410 }
13411 
13412 
13413 void
CopyPlayerNameIntoFileName(char ** dest,char * src)13414 CopyPlayerNameIntoFileName (char **dest, char *src)
13415 {
13416     while (*src != NULLCHAR && *src != ',') {
13417 	if (*src == ' ') {
13418 	    *(*dest)++ = '_';
13419 	    src++;
13420 	} else {
13421 	    *(*dest)++ = *src++;
13422 	}
13423     }
13424 }
13425 
13426 char *
DefaultFileName(char * ext)13427 DefaultFileName (char *ext)
13428 {
13429     static char def[MSG_SIZ];
13430     char *p;
13431 
13432     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13433 	p = def;
13434 	CopyPlayerNameIntoFileName(&p, gameInfo.white);
13435 	*p++ = '-';
13436 	CopyPlayerNameIntoFileName(&p, gameInfo.black);
13437 	*p++ = '.';
13438 	safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13439     } else {
13440 	def[0] = NULLCHAR;
13441     }
13442     return def;
13443 }
13444 
13445 /* Save the current game to the given file */
13446 int
SaveGameToFile(char * filename,int append)13447 SaveGameToFile (char *filename, int append)
13448 {
13449     FILE *f;
13450     char buf[MSG_SIZ];
13451     int result, i, t,tot=0;
13452 
13453     if (strcmp(filename, "-") == 0) {
13454 	return SaveGame(stdout, 0, NULL);
13455     } else {
13456 	for(i=0; i<10; i++) { // upto 10 tries
13457 	     f = fopen(filename, append ? "a" : "w");
13458 	     if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13459 	     if(f || errno != 13) break;
13460 	     DoSleep(t = 5 + random()%11); // wait 5-15 msec
13461 	     tot += t;
13462 	}
13463 	if (f == NULL) {
13464 	    snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13465 	    DisplayError(buf, errno);
13466 	    return FALSE;
13467 	} else {
13468 	    safeStrCpy(buf, lastMsg, MSG_SIZ);
13469 	    DisplayMessage(_("Waiting for access to save file"), "");
13470 	    flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13471 	    DisplayMessage(_("Saving game"), "");
13472 	    if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13473 	    result = SaveGame(f, 0, NULL);
13474 	    DisplayMessage(buf, "");
13475 	    return result;
13476 	}
13477     }
13478 }
13479 
13480 char *
SavePart(char * str)13481 SavePart (char *str)
13482 {
13483     static char buf[MSG_SIZ];
13484     char *p;
13485 
13486     p = strchr(str, ' ');
13487     if (p == NULL) return str;
13488     strncpy(buf, str, p - str);
13489     buf[p - str] = NULLCHAR;
13490     return buf;
13491 }
13492 
13493 #define PGN_MAX_LINE 75
13494 
13495 #define PGN_SIDE_WHITE  0
13496 #define PGN_SIDE_BLACK  1
13497 
13498 static int
FindFirstMoveOutOfBook(int side)13499 FindFirstMoveOutOfBook (int side)
13500 {
13501     int result = -1;
13502 
13503     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13504         int index = backwardMostMove;
13505         int has_book_hit = 0;
13506 
13507         if( (index % 2) != side ) {
13508             index++;
13509         }
13510 
13511         while( index < forwardMostMove ) {
13512             /* Check to see if engine is in book */
13513             int depth = pvInfoList[index].depth;
13514             int score = pvInfoList[index].score;
13515             int in_book = 0;
13516 
13517             if( depth <= 2 ) {
13518                 in_book = 1;
13519             }
13520             else if( score == 0 && depth == 63 ) {
13521                 in_book = 1; /* Zappa */
13522             }
13523             else if( score == 2 && depth == 99 ) {
13524                 in_book = 1; /* Abrok */
13525             }
13526 
13527             has_book_hit += in_book;
13528 
13529             if( ! in_book ) {
13530                 result = index;
13531 
13532                 break;
13533             }
13534 
13535             index += 2;
13536         }
13537     }
13538 
13539     return result;
13540 }
13541 
13542 void
GetOutOfBookInfo(char * buf)13543 GetOutOfBookInfo (char * buf)
13544 {
13545     int oob[2];
13546     int i;
13547     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13548 
13549     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13550     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13551 
13552     *buf = '\0';
13553 
13554     if( oob[0] >= 0 || oob[1] >= 0 ) {
13555         for( i=0; i<2; i++ ) {
13556             int idx = oob[i];
13557 
13558             if( idx >= 0 ) {
13559                 if( i > 0 && oob[0] >= 0 ) {
13560                     strcat( buf, "   " );
13561                 }
13562 
13563                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13564                 sprintf( buf+strlen(buf), "%s%.2f",
13565                     pvInfoList[idx].score >= 0 ? "+" : "",
13566                     pvInfoList[idx].score / 100.0 );
13567             }
13568         }
13569     }
13570 }
13571 
13572 /* Save game in PGN style */
13573 static void
SaveGamePGN2(FILE * f)13574 SaveGamePGN2 (FILE *f)
13575 {
13576     int i, offset, linelen, newblock;
13577 //    char *movetext;
13578     char numtext[32];
13579     int movelen, numlen, blank;
13580     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13581 
13582     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13583 
13584     PrintPGNTags(f, &gameInfo);
13585 
13586     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13587 
13588     if (backwardMostMove > 0 || startedFromSetupPosition) {
13589         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13590         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13591 	fprintf(f, "\n{--------------\n");
13592 	PrintPosition(f, backwardMostMove);
13593 	fprintf(f, "--------------}\n");
13594         free(fen);
13595     }
13596     else {
13597         /* [AS] Out of book annotation */
13598         if( appData.saveOutOfBookInfo ) {
13599             char buf[64];
13600 
13601             GetOutOfBookInfo( buf );
13602 
13603             if( buf[0] != '\0' ) {
13604                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13605             }
13606         }
13607 
13608 	fprintf(f, "\n");
13609     }
13610 
13611     i = backwardMostMove;
13612     linelen = 0;
13613     newblock = TRUE;
13614 
13615     while (i < forwardMostMove) {
13616 	/* Print comments preceding this move */
13617 	if (commentList[i] != NULL) {
13618 	    if (linelen > 0) fprintf(f, "\n");
13619 	    fprintf(f, "%s", commentList[i]);
13620 	    linelen = 0;
13621 	    newblock = TRUE;
13622 	}
13623 
13624 	/* Format move number */
13625 	if ((i % 2) == 0)
13626 	  snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13627         else
13628 	  if (newblock)
13629 	    snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13630 	  else
13631 	    numtext[0] = NULLCHAR;
13632 
13633 	numlen = strlen(numtext);
13634 	newblock = FALSE;
13635 
13636 	/* Print move number */
13637 	blank = linelen > 0 && numlen > 0;
13638 	if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13639 	    fprintf(f, "\n");
13640 	    linelen = 0;
13641 	    blank = 0;
13642 	}
13643 	if (blank) {
13644 	    fprintf(f, " ");
13645 	    linelen++;
13646 	}
13647 	fprintf(f, "%s", numtext);
13648 	linelen += numlen;
13649 
13650 	/* Get move */
13651 	safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13652 	movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13653 
13654 	/* Print move */
13655 	blank = linelen > 0 && movelen > 0;
13656 	if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13657 	    fprintf(f, "\n");
13658 	    linelen = 0;
13659 	    blank = 0;
13660 	}
13661 	if (blank) {
13662 	    fprintf(f, " ");
13663 	    linelen++;
13664 	}
13665 	fprintf(f, "%s", move_buffer);
13666 	linelen += movelen;
13667 
13668         /* [AS] Add PV info if present */
13669         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13670             /* [HGM] add time */
13671             char buf[MSG_SIZ]; int seconds;
13672 
13673             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13674 
13675             if( seconds <= 0)
13676 	      buf[0] = 0;
13677 	    else
13678 	      if( seconds < 30 )
13679 		snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13680 	      else
13681 		{
13682 		  seconds = (seconds + 4)/10; // round to full seconds
13683 		  if( seconds < 60 )
13684 		    snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13685 		  else
13686 		    snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13687 		}
13688 
13689             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13690 		      pvInfoList[i].score >= 0 ? "+" : "",
13691 		      pvInfoList[i].score / 100.0,
13692 		      pvInfoList[i].depth,
13693 		      buf );
13694 
13695 	    movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13696 
13697 	    /* Print score/depth */
13698 	    blank = linelen > 0 && movelen > 0;
13699 	    if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13700 		fprintf(f, "\n");
13701 		linelen = 0;
13702 		blank = 0;
13703 	    }
13704 	    if (blank) {
13705 		fprintf(f, " ");
13706 		linelen++;
13707 	    }
13708 	    fprintf(f, "%s", move_buffer);
13709 	    linelen += movelen;
13710         }
13711 
13712 	i++;
13713     }
13714 
13715     /* Start a new line */
13716     if (linelen > 0) fprintf(f, "\n");
13717 
13718     /* Print comments after last move */
13719     if (commentList[i] != NULL) {
13720 	fprintf(f, "%s\n", commentList[i]);
13721     }
13722 
13723     /* Print result */
13724     if (gameInfo.resultDetails != NULL &&
13725 	gameInfo.resultDetails[0] != NULLCHAR) {
13726 	char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13727 	if(gameInfo.result == GameUnfinished && appData.clockMode &&
13728 	   (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13729 	    snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13730 	fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13731     } else {
13732 	fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13733     }
13734 }
13735 
13736 /* Save game in PGN style and close the file */
13737 int
SaveGamePGN(FILE * f)13738 SaveGamePGN (FILE *f)
13739 {
13740     SaveGamePGN2(f);
13741     fclose(f);
13742     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13743     return TRUE;
13744 }
13745 
13746 /* Save game in old style and close the file */
13747 int
SaveGameOldStyle(FILE * f)13748 SaveGameOldStyle (FILE *f)
13749 {
13750     int i, offset;
13751     time_t tm;
13752 
13753     tm = time((time_t *) NULL);
13754 
13755     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13756     PrintOpponents(f);
13757 
13758     if (backwardMostMove > 0 || startedFromSetupPosition) {
13759 	fprintf(f, "\n[--------------\n");
13760 	PrintPosition(f, backwardMostMove);
13761 	fprintf(f, "--------------]\n");
13762     } else {
13763 	fprintf(f, "\n");
13764     }
13765 
13766     i = backwardMostMove;
13767     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13768 
13769     while (i < forwardMostMove) {
13770 	if (commentList[i] != NULL) {
13771 	    fprintf(f, "[%s]\n", commentList[i]);
13772 	}
13773 
13774 	if ((i % 2) == 1) {
13775 	    fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13776 	    i++;
13777 	} else {
13778 	    fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13779 	    i++;
13780 	    if (commentList[i] != NULL) {
13781 		fprintf(f, "\n");
13782 		continue;
13783 	    }
13784 	    if (i >= forwardMostMove) {
13785 		fprintf(f, "\n");
13786 		break;
13787 	    }
13788 	    fprintf(f, "%s\n", parseList[i]);
13789 	    i++;
13790 	}
13791     }
13792 
13793     if (commentList[i] != NULL) {
13794 	fprintf(f, "[%s]\n", commentList[i]);
13795     }
13796 
13797     /* This isn't really the old style, but it's close enough */
13798     if (gameInfo.resultDetails != NULL &&
13799 	gameInfo.resultDetails[0] != NULLCHAR) {
13800 	fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13801 		gameInfo.resultDetails);
13802     } else {
13803 	fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13804     }
13805 
13806     fclose(f);
13807     return TRUE;
13808 }
13809 
13810 /* Save the current game to open file f and close the file */
13811 int
SaveGame(FILE * f,int dummy,char * dummy2)13812 SaveGame (FILE *f, int dummy, char *dummy2)
13813 {
13814     if (gameMode == EditPosition) EditPositionDone(TRUE);
13815     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13816     if (appData.oldSaveStyle)
13817       return SaveGameOldStyle(f);
13818     else
13819       return SaveGamePGN(f);
13820 }
13821 
13822 /* Save the current position to the given file */
13823 int
SavePositionToFile(char * filename)13824 SavePositionToFile (char *filename)
13825 {
13826     FILE *f;
13827     char buf[MSG_SIZ];
13828 
13829     if (strcmp(filename, "-") == 0) {
13830 	return SavePosition(stdout, 0, NULL);
13831     } else {
13832 	f = fopen(filename, "a");
13833 	if (f == NULL) {
13834 	    snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13835 	    DisplayError(buf, errno);
13836 	    return FALSE;
13837 	} else {
13838 	    safeStrCpy(buf, lastMsg, MSG_SIZ);
13839 	    DisplayMessage(_("Waiting for access to save file"), "");
13840 	    flock(fileno(f), LOCK_EX); // [HGM] lock
13841 	    DisplayMessage(_("Saving position"), "");
13842 	    lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13843 	    SavePosition(f, 0, NULL);
13844 	    DisplayMessage(buf, "");
13845 	    return TRUE;
13846 	}
13847     }
13848 }
13849 
13850 /* Save the current position to the given open file and close the file */
13851 int
SavePosition(FILE * f,int dummy,char * dummy2)13852 SavePosition (FILE *f, int dummy, char *dummy2)
13853 {
13854     time_t tm;
13855     char *fen;
13856 
13857     if (gameMode == EditPosition) EditPositionDone(TRUE);
13858     if (appData.oldSaveStyle) {
13859 	tm = time((time_t *) NULL);
13860 
13861 	fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13862 	PrintOpponents(f);
13863 	fprintf(f, "[--------------\n");
13864 	PrintPosition(f, currentMove);
13865 	fprintf(f, "--------------]\n");
13866     } else {
13867 	fen = PositionToFEN(currentMove, NULL, 1);
13868 	fprintf(f, "%s\n", fen);
13869 	free(fen);
13870     }
13871     fclose(f);
13872     return TRUE;
13873 }
13874 
13875 void
ReloadCmailMsgEvent(int unregister)13876 ReloadCmailMsgEvent (int unregister)
13877 {
13878 #if !WIN32
13879     static char *inFilename = NULL;
13880     static char *outFilename;
13881     int i;
13882     struct stat inbuf, outbuf;
13883     int status;
13884 
13885     /* Any registered moves are unregistered if unregister is set, */
13886     /* i.e. invoked by the signal handler */
13887     if (unregister) {
13888 	for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13889 	    cmailMoveRegistered[i] = FALSE;
13890 	    if (cmailCommentList[i] != NULL) {
13891 		free(cmailCommentList[i]);
13892 		cmailCommentList[i] = NULL;
13893 	    }
13894 	}
13895 	nCmailMovesRegistered = 0;
13896     }
13897 
13898     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13899 	cmailResult[i] = CMAIL_NOT_RESULT;
13900     }
13901     nCmailResults = 0;
13902 
13903     if (inFilename == NULL) {
13904 	/* Because the filenames are static they only get malloced once  */
13905 	/* and they never get freed                                      */
13906 	inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13907 	sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13908 
13909 	outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13910 	sprintf(outFilename, "%s.out", appData.cmailGameName);
13911     }
13912 
13913     status = stat(outFilename, &outbuf);
13914     if (status < 0) {
13915 	cmailMailedMove = FALSE;
13916     } else {
13917 	status = stat(inFilename, &inbuf);
13918 	cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13919     }
13920 
13921     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13922        counts the games, notes how each one terminated, etc.
13923 
13924        It would be nice to remove this kludge and instead gather all
13925        the information while building the game list.  (And to keep it
13926        in the game list nodes instead of having a bunch of fixed-size
13927        parallel arrays.)  Note this will require getting each game's
13928        termination from the PGN tags, as the game list builder does
13929        not process the game moves.  --mann
13930        */
13931     cmailMsgLoaded = TRUE;
13932     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13933 
13934     /* Load first game in the file or popup game menu */
13935     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13936 
13937 #endif /* !WIN32 */
13938     return;
13939 }
13940 
13941 int
RegisterMove()13942 RegisterMove ()
13943 {
13944     FILE *f;
13945     char string[MSG_SIZ];
13946 
13947     if (   cmailMailedMove
13948 	|| (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13949 	return TRUE;		/* Allow free viewing  */
13950     }
13951 
13952     /* Unregister move to ensure that we don't leave RegisterMove        */
13953     /* with the move registered when the conditions for registering no   */
13954     /* longer hold                                                       */
13955     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13956 	cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13957 	nCmailMovesRegistered --;
13958 
13959 	if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13960 	  {
13961 	      free(cmailCommentList[lastLoadGameNumber - 1]);
13962 	      cmailCommentList[lastLoadGameNumber - 1] = NULL;
13963 	  }
13964     }
13965 
13966     if (cmailOldMove == -1) {
13967 	DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13968 	return FALSE;
13969     }
13970 
13971     if (currentMove > cmailOldMove + 1) {
13972 	DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13973 	return FALSE;
13974     }
13975 
13976     if (currentMove < cmailOldMove) {
13977 	DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13978 	return FALSE;
13979     }
13980 
13981     if (forwardMostMove > currentMove) {
13982 	/* Silently truncate extra moves */
13983 	TruncateGame();
13984     }
13985 
13986     if (   (currentMove == cmailOldMove + 1)
13987 	|| (   (currentMove == cmailOldMove)
13988 	    && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13989 		|| (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13990 	if (gameInfo.result != GameUnfinished) {
13991 	    cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13992 	}
13993 
13994 	if (commentList[currentMove] != NULL) {
13995 	    cmailCommentList[lastLoadGameNumber - 1]
13996 	      = StrSave(commentList[currentMove]);
13997 	}
13998 	safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13999 
14000 	if (appData.debugMode)
14001 	  fprintf(debugFP, "Saving %s for game %d\n",
14002 		  cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14003 
14004 	snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14005 
14006 	f = fopen(string, "w");
14007 	if (appData.oldSaveStyle) {
14008 	    SaveGameOldStyle(f); /* also closes the file */
14009 
14010 	    snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14011 	    f = fopen(string, "w");
14012 	    SavePosition(f, 0, NULL); /* also closes the file */
14013 	} else {
14014 	    fprintf(f, "{--------------\n");
14015 	    PrintPosition(f, currentMove);
14016 	    fprintf(f, "--------------}\n\n");
14017 
14018 	    SaveGame(f, 0, NULL); /* also closes the file*/
14019 	}
14020 
14021 	cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14022 	nCmailMovesRegistered ++;
14023     } else if (nCmailGames == 1) {
14024 	DisplayError(_("You have not made a move yet"), 0);
14025 	return FALSE;
14026     }
14027 
14028     return TRUE;
14029 }
14030 
14031 void
MailMoveEvent()14032 MailMoveEvent ()
14033 {
14034 #if !WIN32
14035     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14036     FILE *commandOutput;
14037     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14038     int nBytes = 0;		/*  Suppress warnings on uninitialized variables    */
14039     int nBuffers;
14040     int i;
14041     int archived;
14042     char *arcDir;
14043 
14044     if (! cmailMsgLoaded) {
14045 	DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14046 	return;
14047     }
14048 
14049     if (nCmailGames == nCmailResults) {
14050 	DisplayError(_("No unfinished games"), 0);
14051 	return;
14052     }
14053 
14054 #if CMAIL_PROHIBIT_REMAIL
14055     if (cmailMailedMove) {
14056       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);
14057 	DisplayError(msg, 0);
14058 	return;
14059     }
14060 #endif
14061 
14062     if (! (cmailMailedMove || RegisterMove())) return;
14063 
14064     if (   cmailMailedMove
14065 	|| (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14066       snprintf(string, MSG_SIZ, partCommandString,
14067 	       appData.debugMode ? " -v" : "", appData.cmailGameName);
14068 	commandOutput = popen(string, "r");
14069 
14070 	if (commandOutput == NULL) {
14071 	    DisplayError(_("Failed to invoke cmail"), 0);
14072 	} else {
14073 	    for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14074 		nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14075 	    }
14076 	    if (nBuffers > 1) {
14077 		(void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14078 		(void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14079 		nBytes = MSG_SIZ - 1;
14080 	    } else {
14081 		(void) memcpy(msg, buffer, nBytes);
14082 	    }
14083 	    *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14084 
14085 	    if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14086 		cmailMailedMove = TRUE; /* Prevent >1 moves    */
14087 
14088 		archived = TRUE;
14089 		for (i = 0; i < nCmailGames; i ++) {
14090 		    if (cmailResult[i] == CMAIL_NOT_RESULT) {
14091 			archived = FALSE;
14092 		    }
14093 		}
14094 		if (   archived
14095 		    && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14096 			!= NULL)) {
14097 		  snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14098 			   arcDir,
14099 			   appData.cmailGameName,
14100 			   gameInfo.date);
14101 		    LoadGameFromFile(buffer, 1, buffer, FALSE);
14102 		    cmailMsgLoaded = FALSE;
14103 		}
14104 	    }
14105 
14106 	    DisplayInformation(msg);
14107 	    pclose(commandOutput);
14108 	}
14109     } else {
14110 	if ((*cmailMsg) != '\0') {
14111 	    DisplayInformation(cmailMsg);
14112 	}
14113     }
14114 
14115     return;
14116 #endif /* !WIN32 */
14117 }
14118 
14119 char *
CmailMsg()14120 CmailMsg ()
14121 {
14122 #if WIN32
14123     return NULL;
14124 #else
14125     int  prependComma = 0;
14126     char number[5];
14127     char string[MSG_SIZ];	/* Space for game-list */
14128     int  i;
14129 
14130     if (!cmailMsgLoaded) return "";
14131 
14132     if (cmailMailedMove) {
14133       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14134     } else {
14135 	/* Create a list of games left */
14136       snprintf(string, MSG_SIZ, "[");
14137 	for (i = 0; i < nCmailGames; i ++) {
14138 	    if (! (   cmailMoveRegistered[i]
14139 		   || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14140 		if (prependComma) {
14141 		    snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14142 		} else {
14143 		    snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14144 		    prependComma = 1;
14145 		}
14146 
14147 		strcat(string, number);
14148 	    }
14149 	}
14150 	strcat(string, "]");
14151 
14152 	if (nCmailMovesRegistered + nCmailResults == 0) {
14153 	    switch (nCmailGames) {
14154 	      case 1:
14155 		snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14156 		break;
14157 
14158 	      case 2:
14159 		snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14160 		break;
14161 
14162 	      default:
14163 		snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14164 			 nCmailGames);
14165 		break;
14166 	    }
14167 	} else {
14168 	    switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14169 	      case 1:
14170 		snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14171 			 string);
14172 		break;
14173 
14174 	      case 0:
14175 		if (nCmailResults == nCmailGames) {
14176 		  snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14177 		} else {
14178 		  snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14179 		}
14180 		break;
14181 
14182 	      default:
14183 		snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14184 			 string);
14185 	    }
14186 	}
14187     }
14188     return cmailMsg;
14189 #endif /* WIN32 */
14190 }
14191 
14192 void
ResetGameEvent()14193 ResetGameEvent ()
14194 {
14195     if (gameMode == Training)
14196       SetTrainingModeOff();
14197 
14198     Reset(TRUE, TRUE);
14199     cmailMsgLoaded = FALSE;
14200     if (appData.icsActive) {
14201       SendToICS(ics_prefix);
14202       SendToICS("refresh\n");
14203     }
14204 }
14205 
14206 void
ExitEvent(int status)14207 ExitEvent (int status)
14208 {
14209     exiting++;
14210     if (exiting > 2) {
14211       /* Give up on clean exit */
14212       exit(status);
14213     }
14214     if (exiting > 1) {
14215       /* Keep trying for clean exit */
14216       return;
14217     }
14218 
14219     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14220     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14221 
14222     if (telnetISR != NULL) {
14223       RemoveInputSource(telnetISR);
14224     }
14225     if (icsPR != NoProc) {
14226       DestroyChildProcess(icsPR, TRUE);
14227     }
14228 
14229     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14230     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14231 
14232     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14233     /* make sure this other one finishes before killing it!                  */
14234     if(endingGame) { int count = 0;
14235         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14236         while(endingGame && count++ < 10) DoSleep(1);
14237         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14238     }
14239 
14240     /* Kill off chess programs */
14241     if (first.pr != NoProc) {
14242 	ExitAnalyzeMode();
14243 
14244         DoSleep( appData.delayBeforeQuit );
14245 	SendToProgram("quit\n", &first);
14246 	DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14247     }
14248     if (second.pr != NoProc) {
14249         DoSleep( appData.delayBeforeQuit );
14250 	SendToProgram("quit\n", &second);
14251 	DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14252     }
14253     if (first.isr != NULL) {
14254 	RemoveInputSource(first.isr);
14255     }
14256     if (second.isr != NULL) {
14257 	RemoveInputSource(second.isr);
14258     }
14259 
14260     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14261     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14262 
14263     ShutDownFrontEnd();
14264     exit(status);
14265 }
14266 
14267 void
PauseEngine(ChessProgramState * cps)14268 PauseEngine (ChessProgramState *cps)
14269 {
14270     SendToProgram("pause\n", cps);
14271     cps->pause = 2;
14272 }
14273 
14274 void
UnPauseEngine(ChessProgramState * cps)14275 UnPauseEngine (ChessProgramState *cps)
14276 {
14277     SendToProgram("resume\n", cps);
14278     cps->pause = 1;
14279 }
14280 
14281 void
PauseEvent()14282 PauseEvent ()
14283 {
14284     if (appData.debugMode)
14285 	fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14286     if (pausing) {
14287 	pausing = FALSE;
14288 	ModeHighlight();
14289 	if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14290 	    StartClocks();
14291 	    if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14292 		if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14293 		else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14294 	    }
14295 	    if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14296 	    HandleMachineMove(stashedInputMove, stalledEngine);
14297 	    stalledEngine = NULL;
14298 	    return;
14299 	}
14300 	if (gameMode == MachinePlaysWhite ||
14301 	    gameMode == TwoMachinesPlay   ||
14302 	    gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14303 	    if(first.pause)  UnPauseEngine(&first);
14304 	    else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14305 	    if(second.pause) UnPauseEngine(&second);
14306 	    else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14307 	    StartClocks();
14308 	} else {
14309 	    DisplayBothClocks();
14310 	}
14311 	if (gameMode == PlayFromGameFile) {
14312 	    if (appData.timeDelay >= 0)
14313 		AutoPlayGameLoop();
14314 	} else if (gameMode == IcsExamining && pauseExamInvalid) {
14315 	    Reset(FALSE, TRUE);
14316 	    SendToICS(ics_prefix);
14317 	    SendToICS("refresh\n");
14318 	} else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14319 	    ForwardInner(forwardMostMove);
14320 	}
14321 	pauseExamInvalid = FALSE;
14322     } else {
14323 	switch (gameMode) {
14324 	  default:
14325 	    return;
14326 	  case IcsExamining:
14327 	    pauseExamForwardMostMove = forwardMostMove;
14328 	    pauseExamInvalid = FALSE;
14329 	    /* fall through */
14330 	  case IcsObserving:
14331 	  case IcsPlayingWhite:
14332 	  case IcsPlayingBlack:
14333 	    pausing = TRUE;
14334 	    ModeHighlight();
14335 	    return;
14336 	  case PlayFromGameFile:
14337 	    (void) StopLoadGameTimer();
14338 	    pausing = TRUE;
14339 	    ModeHighlight();
14340 	    break;
14341 	  case BeginningOfGame:
14342 	    if (appData.icsActive) return;
14343 	    /* else fall through */
14344 	  case MachinePlaysWhite:
14345 	  case MachinePlaysBlack:
14346 	  case TwoMachinesPlay:
14347 	    if (forwardMostMove == 0)
14348 	      return;		/* don't pause if no one has moved */
14349 	    if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14350 		ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14351 		if(onMove->pause) {           // thinking engine can be paused
14352 		    PauseEngine(onMove);      // do it
14353 		    if(onMove->other->pause)  // pondering opponent can always be paused immediately
14354 			PauseEngine(onMove->other);
14355 		    else
14356 			SendToProgram("easy\n", onMove->other);
14357 		    StopClocks();
14358 		} else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14359 	    } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14360 		if(first.pause) {
14361 		    PauseEngine(&first);
14362 		    StopClocks();
14363 		} else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14364 	    } else { // human on move, pause pondering by either method
14365 		if(first.pause)
14366 		    PauseEngine(&first);
14367 		else if(appData.ponderNextMove)
14368 		    SendToProgram("easy\n", &first);
14369 		StopClocks();
14370 	    }
14371 	    // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14372 	  case AnalyzeMode:
14373 	    pausing = TRUE;
14374 	    ModeHighlight();
14375 	    break;
14376 	}
14377     }
14378 }
14379 
14380 void
EditCommentEvent()14381 EditCommentEvent ()
14382 {
14383     char title[MSG_SIZ];
14384 
14385     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14386       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14387     } else {
14388       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14389 	       WhiteOnMove(currentMove - 1) ? " " : ".. ",
14390 	       parseList[currentMove - 1]);
14391     }
14392 
14393     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14394 }
14395 
14396 
14397 void
EditTagsEvent()14398 EditTagsEvent ()
14399 {
14400     char *tags = PGNTags(&gameInfo);
14401     bookUp = FALSE;
14402     EditTagsPopUp(tags, NULL);
14403     free(tags);
14404 }
14405 
14406 void
ToggleSecond()14407 ToggleSecond ()
14408 {
14409   if(second.analyzing) {
14410     SendToProgram("exit\n", &second);
14411     second.analyzing = FALSE;
14412   } else {
14413     if (second.pr == NoProc) StartChessProgram(&second);
14414     InitChessProgram(&second, FALSE);
14415     FeedMovesToProgram(&second, currentMove);
14416 
14417     SendToProgram("analyze\n", &second);
14418     second.analyzing = TRUE;
14419   }
14420 }
14421 
14422 /* Toggle ShowThinking */
14423 void
ToggleShowThinking()14424 ToggleShowThinking()
14425 {
14426   appData.showThinking = !appData.showThinking;
14427   ShowThinkingEvent();
14428 }
14429 
14430 int
AnalyzeModeEvent()14431 AnalyzeModeEvent ()
14432 {
14433     char buf[MSG_SIZ];
14434 
14435     if (!first.analysisSupport) {
14436       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14437       DisplayError(buf, 0);
14438       return 0;
14439     }
14440     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14441     if (appData.icsActive) {
14442         if (gameMode != IcsObserving) {
14443 	  snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14444             DisplayError(buf, 0);
14445             /* secure check */
14446             if (appData.icsEngineAnalyze) {
14447                 if (appData.debugMode)
14448                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14449                 ExitAnalyzeMode();
14450                 ModeHighlight();
14451             }
14452             return 0;
14453         }
14454         /* if enable, user wants to disable icsEngineAnalyze */
14455         if (appData.icsEngineAnalyze) {
14456                 ExitAnalyzeMode();
14457                 ModeHighlight();
14458                 return 0;
14459         }
14460         appData.icsEngineAnalyze = TRUE;
14461         if (appData.debugMode)
14462             fprintf(debugFP, "ICS engine analyze starting... \n");
14463     }
14464 
14465     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14466     if (appData.noChessProgram || gameMode == AnalyzeMode)
14467       return 0;
14468 
14469     if (gameMode != AnalyzeFile) {
14470         if (!appData.icsEngineAnalyze) {
14471                EditGameEvent();
14472                if (gameMode != EditGame) return 0;
14473         }
14474 	if (!appData.showThinking) ToggleShowThinking();
14475 	ResurrectChessProgram();
14476 	SendToProgram("analyze\n", &first);
14477 	first.analyzing = TRUE;
14478 	/*first.maybeThinking = TRUE;*/
14479 	first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14480 	EngineOutputPopUp();
14481     }
14482     if (!appData.icsEngineAnalyze) {
14483 	gameMode = AnalyzeMode;
14484 	ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14485     }
14486     pausing = FALSE;
14487     ModeHighlight();
14488     SetGameInfo();
14489 
14490     StartAnalysisClock();
14491     GetTimeMark(&lastNodeCountTime);
14492     lastNodeCount = 0;
14493     return 1;
14494 }
14495 
14496 void
AnalyzeFileEvent()14497 AnalyzeFileEvent ()
14498 {
14499     if (appData.noChessProgram || gameMode == AnalyzeFile)
14500       return;
14501 
14502     if (!first.analysisSupport) {
14503       char buf[MSG_SIZ];
14504       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14505       DisplayError(buf, 0);
14506       return;
14507     }
14508 
14509     if (gameMode != AnalyzeMode) {
14510 	keepInfo = 1; // mere annotating should not alter PGN tags
14511 	EditGameEvent();
14512 	keepInfo = 0;
14513 	if (gameMode != EditGame) return;
14514 	if (!appData.showThinking) ToggleShowThinking();
14515 	ResurrectChessProgram();
14516 	SendToProgram("analyze\n", &first);
14517 	first.analyzing = TRUE;
14518 	/*first.maybeThinking = TRUE;*/
14519 	first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14520 	EngineOutputPopUp();
14521     }
14522     gameMode = AnalyzeFile;
14523     pausing = FALSE;
14524     ModeHighlight();
14525 
14526     StartAnalysisClock();
14527     GetTimeMark(&lastNodeCountTime);
14528     lastNodeCount = 0;
14529     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14530     AnalysisPeriodicEvent(1);
14531 }
14532 
14533 void
MachineWhiteEvent()14534 MachineWhiteEvent ()
14535 {
14536     char buf[MSG_SIZ];
14537     char *bookHit = NULL;
14538 
14539     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14540       return;
14541 
14542 
14543     if (gameMode == PlayFromGameFile ||
14544 	gameMode == TwoMachinesPlay  ||
14545 	gameMode == Training         ||
14546 	gameMode == AnalyzeMode      ||
14547 	gameMode == EndOfGame)
14548 	EditGameEvent();
14549 
14550     if (gameMode == EditPosition)
14551         EditPositionDone(TRUE);
14552 
14553     if (!WhiteOnMove(currentMove)) {
14554 	DisplayError(_("It is not White's turn"), 0);
14555 	return;
14556     }
14557 
14558     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14559       ExitAnalyzeMode();
14560 
14561     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14562 	gameMode == AnalyzeFile)
14563 	TruncateGame();
14564 
14565     ResurrectChessProgram();	/* in case it isn't running */
14566     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14567 	gameMode = MachinePlaysWhite;
14568 	ResetClocks();
14569     } else
14570     gameMode = MachinePlaysWhite;
14571     pausing = FALSE;
14572     ModeHighlight();
14573     SetGameInfo();
14574     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14575     DisplayTitle(buf);
14576     if (first.sendName) {
14577       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14578       SendToProgram(buf, &first);
14579     }
14580     if (first.sendTime) {
14581       if (first.useColors) {
14582 	SendToProgram("black\n", &first); /*gnu kludge*/
14583       }
14584       SendTimeRemaining(&first, TRUE);
14585     }
14586     if (first.useColors) {
14587       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14588     }
14589     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14590     SetMachineThinkingEnables();
14591     first.maybeThinking = TRUE;
14592     StartClocks();
14593     firstMove = FALSE;
14594 
14595     if (appData.autoFlipView && !flipView) {
14596       flipView = !flipView;
14597       DrawPosition(FALSE, NULL);
14598       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14599     }
14600 
14601     if(bookHit) { // [HGM] book: simulate book reply
14602 	static char bookMove[MSG_SIZ]; // a bit generous?
14603 
14604 	programStats.nodes = programStats.depth = programStats.time =
14605 	programStats.score = programStats.got_only_move = 0;
14606 	sprintf(programStats.movelist, "%s (xbook)", bookHit);
14607 
14608 	safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14609 	strcat(bookMove, bookHit);
14610 	HandleMachineMove(bookMove, &first);
14611     }
14612 }
14613 
14614 void
MachineBlackEvent()14615 MachineBlackEvent ()
14616 {
14617   char buf[MSG_SIZ];
14618   char *bookHit = NULL;
14619 
14620     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14621 	return;
14622 
14623 
14624     if (gameMode == PlayFromGameFile ||
14625 	gameMode == TwoMachinesPlay  ||
14626 	gameMode == Training         ||
14627 	gameMode == AnalyzeMode      ||
14628 	gameMode == EndOfGame)
14629         EditGameEvent();
14630 
14631     if (gameMode == EditPosition)
14632         EditPositionDone(TRUE);
14633 
14634     if (WhiteOnMove(currentMove)) {
14635 	DisplayError(_("It is not Black's turn"), 0);
14636 	return;
14637     }
14638 
14639     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14640       ExitAnalyzeMode();
14641 
14642     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14643 	gameMode == AnalyzeFile)
14644 	TruncateGame();
14645 
14646     ResurrectChessProgram();	/* in case it isn't running */
14647     gameMode = MachinePlaysBlack;
14648     pausing = FALSE;
14649     ModeHighlight();
14650     SetGameInfo();
14651     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14652     DisplayTitle(buf);
14653     if (first.sendName) {
14654       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14655       SendToProgram(buf, &first);
14656     }
14657     if (first.sendTime) {
14658       if (first.useColors) {
14659 	SendToProgram("white\n", &first); /*gnu kludge*/
14660       }
14661       SendTimeRemaining(&first, FALSE);
14662     }
14663     if (first.useColors) {
14664       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14665     }
14666     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14667     SetMachineThinkingEnables();
14668     first.maybeThinking = TRUE;
14669     StartClocks();
14670 
14671     if (appData.autoFlipView && flipView) {
14672       flipView = !flipView;
14673       DrawPosition(FALSE, NULL);
14674       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14675     }
14676     if(bookHit) { // [HGM] book: simulate book reply
14677 	static char bookMove[MSG_SIZ]; // a bit generous?
14678 
14679 	programStats.nodes = programStats.depth = programStats.time =
14680 	programStats.score = programStats.got_only_move = 0;
14681 	sprintf(programStats.movelist, "%s (xbook)", bookHit);
14682 
14683 	safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14684 	strcat(bookMove, bookHit);
14685 	HandleMachineMove(bookMove, &first);
14686     }
14687 }
14688 
14689 
14690 void
DisplayTwoMachinesTitle()14691 DisplayTwoMachinesTitle ()
14692 {
14693     char buf[MSG_SIZ];
14694     if (appData.matchGames > 0) {
14695         if(appData.tourneyFile[0]) {
14696 	  snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14697 		   gameInfo.white, _("vs."), gameInfo.black,
14698 		   nextGame+1, appData.matchGames+1,
14699 		   appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14700         } else
14701         if (first.twoMachinesColor[0] == 'w') {
14702 	  snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14703 		   gameInfo.white, _("vs."),  gameInfo.black,
14704 		   first.matchWins, second.matchWins,
14705 		   matchGame - 1 - (first.matchWins + second.matchWins));
14706 	} else {
14707 	  snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14708 		   gameInfo.white, _("vs."), gameInfo.black,
14709 		   second.matchWins, first.matchWins,
14710 		   matchGame - 1 - (first.matchWins + second.matchWins));
14711 	}
14712     } else {
14713       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14714     }
14715     DisplayTitle(buf);
14716 }
14717 
14718 void
SettingsMenuIfReady()14719 SettingsMenuIfReady ()
14720 {
14721   if (second.lastPing != second.lastPong) {
14722     DisplayMessage("", _("Waiting for second chess program"));
14723     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14724     return;
14725   }
14726   ThawUI();
14727   DisplayMessage("", "");
14728   SettingsPopUp(&second);
14729 }
14730 
14731 int
WaitForEngine(ChessProgramState * cps,DelayedEventCallback retry)14732 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14733 {
14734     char buf[MSG_SIZ];
14735     if (cps->pr == NoProc) {
14736 	StartChessProgram(cps);
14737 	if (cps->protocolVersion == 1) {
14738 	  retry();
14739 	  ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14740 	} else {
14741 	  /* kludge: allow timeout for initial "feature" command */
14742 	  if(retry != TwoMachinesEventIfReady) FreezeUI();
14743 	  snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14744 	  DisplayMessage("", buf);
14745 	  ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14746 	}
14747 	return 1;
14748     }
14749     return 0;
14750 }
14751 
14752 void
TwoMachinesEvent(void)14753 TwoMachinesEvent P((void))
14754 {
14755     int i;
14756     char buf[MSG_SIZ];
14757     ChessProgramState *onmove;
14758     char *bookHit = NULL;
14759     static int stalling = 0;
14760     TimeMark now;
14761     long wait;
14762 
14763     if (appData.noChessProgram) return;
14764 
14765     switch (gameMode) {
14766       case TwoMachinesPlay:
14767 	return;
14768       case MachinePlaysWhite:
14769       case MachinePlaysBlack:
14770 	if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14771 	    DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14772 	    return;
14773 	}
14774 	/* fall through */
14775       case BeginningOfGame:
14776       case PlayFromGameFile:
14777       case EndOfGame:
14778 	EditGameEvent();
14779 	if (gameMode != EditGame) return;
14780 	break;
14781       case EditPosition:
14782 	EditPositionDone(TRUE);
14783 	break;
14784       case AnalyzeMode:
14785       case AnalyzeFile:
14786 	ExitAnalyzeMode();
14787 	break;
14788       case EditGame:
14789       default:
14790 	break;
14791     }
14792 
14793 //    forwardMostMove = currentMove;
14794     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14795     startingEngine = TRUE;
14796 
14797     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14798 
14799     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14800     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14801       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14802       return;
14803     }
14804     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14805 
14806     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14807                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14808 	startingEngine = matchMode = FALSE;
14809 	DisplayError("second engine does not play this", 0);
14810 	gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14811 	EditGameEvent(); // switch back to EditGame mode
14812 	return;
14813     }
14814 
14815     if(!stalling) {
14816       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14817       SendToProgram("force\n", &second);
14818       stalling = 1;
14819       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14820       return;
14821     }
14822     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14823     if(appData.matchPause>10000 || appData.matchPause<10)
14824                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14825     wait = SubtractTimeMarks(&now, &pauseStart);
14826     if(wait < appData.matchPause) {
14827 	ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14828 	return;
14829     }
14830     // we are now committed to starting the game
14831     stalling = 0;
14832     DisplayMessage("", "");
14833     if (startedFromSetupPosition) {
14834 	SendBoard(&second, backwardMostMove);
14835     if (appData.debugMode) {
14836         fprintf(debugFP, "Two Machines\n");
14837     }
14838     }
14839     for (i = backwardMostMove; i < forwardMostMove; i++) {
14840 	SendMoveToProgram(i, &second);
14841     }
14842 
14843     gameMode = TwoMachinesPlay;
14844     pausing = startingEngine = FALSE;
14845     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14846     SetGameInfo();
14847     DisplayTwoMachinesTitle();
14848     firstMove = TRUE;
14849     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14850 	onmove = &first;
14851     } else {
14852 	onmove = &second;
14853     }
14854     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14855     SendToProgram(first.computerString, &first);
14856     if (first.sendName) {
14857       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14858       SendToProgram(buf, &first);
14859     }
14860     SendToProgram(second.computerString, &second);
14861     if (second.sendName) {
14862       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14863       SendToProgram(buf, &second);
14864     }
14865 
14866     ResetClocks();
14867     if (!first.sendTime || !second.sendTime) {
14868 	timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14869 	timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14870     }
14871     if (onmove->sendTime) {
14872       if (onmove->useColors) {
14873 	SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14874       }
14875       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14876     }
14877     if (onmove->useColors) {
14878       SendToProgram(onmove->twoMachinesColor, onmove);
14879     }
14880     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14881 //    SendToProgram("go\n", onmove);
14882     onmove->maybeThinking = TRUE;
14883     SetMachineThinkingEnables();
14884 
14885     StartClocks();
14886 
14887     if(bookHit) { // [HGM] book: simulate book reply
14888 	static char bookMove[MSG_SIZ]; // a bit generous?
14889 
14890 	programStats.nodes = programStats.depth = programStats.time =
14891 	programStats.score = programStats.got_only_move = 0;
14892 	sprintf(programStats.movelist, "%s (xbook)", bookHit);
14893 
14894 	safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14895 	strcat(bookMove, bookHit);
14896 	savedMessage = bookMove; // args for deferred call
14897 	savedState = onmove;
14898 	ScheduleDelayedEvent(DeferredBookMove, 1);
14899     }
14900 }
14901 
14902 void
TrainingEvent()14903 TrainingEvent ()
14904 {
14905     if (gameMode == Training) {
14906       SetTrainingModeOff();
14907       gameMode = PlayFromGameFile;
14908       DisplayMessage("", _("Training mode off"));
14909     } else {
14910       gameMode = Training;
14911       animateTraining = appData.animate;
14912 
14913       /* make sure we are not already at the end of the game */
14914       if (currentMove < forwardMostMove) {
14915 	SetTrainingModeOn();
14916 	DisplayMessage("", _("Training mode on"));
14917       } else {
14918 	gameMode = PlayFromGameFile;
14919 	DisplayError(_("Already at end of game"), 0);
14920       }
14921     }
14922     ModeHighlight();
14923 }
14924 
14925 void
IcsClientEvent()14926 IcsClientEvent ()
14927 {
14928     if (!appData.icsActive) return;
14929     switch (gameMode) {
14930       case IcsPlayingWhite:
14931       case IcsPlayingBlack:
14932       case IcsObserving:
14933       case IcsIdle:
14934       case BeginningOfGame:
14935       case IcsExamining:
14936 	return;
14937 
14938       case EditGame:
14939 	break;
14940 
14941       case EditPosition:
14942 	EditPositionDone(TRUE);
14943 	break;
14944 
14945       case AnalyzeMode:
14946       case AnalyzeFile:
14947 	ExitAnalyzeMode();
14948 	break;
14949 
14950       default:
14951 	EditGameEvent();
14952 	break;
14953     }
14954 
14955     gameMode = IcsIdle;
14956     ModeHighlight();
14957     return;
14958 }
14959 
14960 void
EditGameEvent()14961 EditGameEvent ()
14962 {
14963     int i;
14964 
14965     switch (gameMode) {
14966       case Training:
14967 	SetTrainingModeOff();
14968 	break;
14969       case MachinePlaysWhite:
14970       case MachinePlaysBlack:
14971       case BeginningOfGame:
14972 	SendToProgram("force\n", &first);
14973 	SetUserThinkingEnables();
14974 	break;
14975       case PlayFromGameFile:
14976 	(void) StopLoadGameTimer();
14977 	if (gameFileFP != NULL) {
14978 	    gameFileFP = NULL;
14979 	}
14980 	break;
14981       case EditPosition:
14982 	EditPositionDone(TRUE);
14983 	break;
14984       case AnalyzeMode:
14985       case AnalyzeFile:
14986 	ExitAnalyzeMode();
14987 	SendToProgram("force\n", &first);
14988 	break;
14989       case TwoMachinesPlay:
14990 	GameEnds(EndOfFile, NULL, GE_PLAYER);
14991 	ResurrectChessProgram();
14992 	SetUserThinkingEnables();
14993 	break;
14994       case EndOfGame:
14995 	ResurrectChessProgram();
14996 	break;
14997       case IcsPlayingBlack:
14998       case IcsPlayingWhite:
14999 	DisplayError(_("Warning: You are still playing a game"), 0);
15000 	break;
15001       case IcsObserving:
15002 	DisplayError(_("Warning: You are still observing a game"), 0);
15003 	break;
15004       case IcsExamining:
15005 	DisplayError(_("Warning: You are still examining a game"), 0);
15006 	break;
15007       case IcsIdle:
15008 	break;
15009       case EditGame:
15010       default:
15011 	return;
15012     }
15013 
15014     pausing = FALSE;
15015     StopClocks();
15016     first.offeredDraw = second.offeredDraw = 0;
15017 
15018     if (gameMode == PlayFromGameFile) {
15019 	whiteTimeRemaining = timeRemaining[0][currentMove];
15020 	blackTimeRemaining = timeRemaining[1][currentMove];
15021 	DisplayTitle("");
15022     }
15023 
15024     if (gameMode == MachinePlaysWhite ||
15025 	gameMode == MachinePlaysBlack ||
15026 	gameMode == TwoMachinesPlay ||
15027 	gameMode == EndOfGame) {
15028 	i = forwardMostMove;
15029 	while (i > currentMove) {
15030 	    SendToProgram("undo\n", &first);
15031 	    i--;
15032 	}
15033 	if(!adjustedClock) {
15034 	whiteTimeRemaining = timeRemaining[0][currentMove];
15035 	blackTimeRemaining = timeRemaining[1][currentMove];
15036 	DisplayBothClocks();
15037 	}
15038 	if (whiteFlag || blackFlag) {
15039 	    whiteFlag = blackFlag = 0;
15040 	}
15041 	DisplayTitle("");
15042     }
15043 
15044     gameMode = EditGame;
15045     ModeHighlight();
15046     SetGameInfo();
15047 }
15048 
15049 
15050 void
EditPositionEvent()15051 EditPositionEvent ()
15052 {
15053     if (gameMode == EditPosition) {
15054 	EditGameEvent();
15055 	return;
15056     }
15057 
15058     EditGameEvent();
15059     if (gameMode != EditGame) return;
15060 
15061     gameMode = EditPosition;
15062     ModeHighlight();
15063     SetGameInfo();
15064     if (currentMove > 0)
15065       CopyBoard(boards[0], boards[currentMove]);
15066 
15067     blackPlaysFirst = !WhiteOnMove(currentMove);
15068     ResetClocks();
15069     currentMove = forwardMostMove = backwardMostMove = 0;
15070     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15071     DisplayMove(-1);
15072     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15073 }
15074 
15075 void
ExitAnalyzeMode()15076 ExitAnalyzeMode ()
15077 {
15078     /* [DM] icsEngineAnalyze - possible call from other functions */
15079     if (appData.icsEngineAnalyze) {
15080         appData.icsEngineAnalyze = FALSE;
15081 
15082         DisplayMessage("",_("Close ICS engine analyze..."));
15083     }
15084     if (first.analysisSupport && first.analyzing) {
15085       SendToBoth("exit\n");
15086       first.analyzing = second.analyzing = FALSE;
15087     }
15088     thinkOutput[0] = NULLCHAR;
15089 }
15090 
15091 void
EditPositionDone(Boolean fakeRights)15092 EditPositionDone (Boolean fakeRights)
15093 {
15094     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15095 
15096     startedFromSetupPosition = TRUE;
15097     InitChessProgram(&first, FALSE);
15098     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15099       boards[0][EP_STATUS] = EP_NONE;
15100       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15101       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15102 	boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15103 	boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15104       } else boards[0][CASTLING][2] = NoRights;
15105       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15106 	boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15107 	boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15108       } else boards[0][CASTLING][5] = NoRights;
15109       if(gameInfo.variant == VariantSChess) {
15110 	int i;
15111 	for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15112 	  boards[0][VIRGIN][i] = 0;
15113 	  if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15114 	  if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15115 	}
15116       }
15117     }
15118     SendToProgram("force\n", &first);
15119     if (blackPlaysFirst) {
15120         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15121 	safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15122 	currentMove = forwardMostMove = backwardMostMove = 1;
15123 	CopyBoard(boards[1], boards[0]);
15124     } else {
15125 	currentMove = forwardMostMove = backwardMostMove = 0;
15126     }
15127     SendBoard(&first, forwardMostMove);
15128     if (appData.debugMode) {
15129         fprintf(debugFP, "EditPosDone\n");
15130     }
15131     DisplayTitle("");
15132     DisplayMessage("", "");
15133     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15134     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15135     gameMode = EditGame;
15136     ModeHighlight();
15137     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15138     ClearHighlights(); /* [AS] */
15139 }
15140 
15141 /* Pause for `ms' milliseconds */
15142 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15143 void
TimeDelay(long ms)15144 TimeDelay (long ms)
15145 {
15146     TimeMark m1, m2;
15147 
15148     GetTimeMark(&m1);
15149     do {
15150 	GetTimeMark(&m2);
15151     } while (SubtractTimeMarks(&m2, &m1) < ms);
15152 }
15153 
15154 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15155 void
SendMultiLineToICS(char * buf)15156 SendMultiLineToICS (char *buf)
15157 {
15158     char temp[MSG_SIZ+1], *p;
15159     int len;
15160 
15161     len = strlen(buf);
15162     if (len > MSG_SIZ)
15163       len = MSG_SIZ;
15164 
15165     strncpy(temp, buf, len);
15166     temp[len] = 0;
15167 
15168     p = temp;
15169     while (*p) {
15170 	if (*p == '\n' || *p == '\r')
15171 	  *p = ' ';
15172 	++p;
15173     }
15174 
15175     strcat(temp, "\n");
15176     SendToICS(temp);
15177     SendToPlayer(temp, strlen(temp));
15178 }
15179 
15180 void
SetWhiteToPlayEvent()15181 SetWhiteToPlayEvent ()
15182 {
15183     if (gameMode == EditPosition) {
15184 	blackPlaysFirst = FALSE;
15185 	DisplayBothClocks();	/* works because currentMove is 0 */
15186     } else if (gameMode == IcsExamining) {
15187         SendToICS(ics_prefix);
15188 	SendToICS("tomove white\n");
15189     }
15190 }
15191 
15192 void
SetBlackToPlayEvent()15193 SetBlackToPlayEvent ()
15194 {
15195     if (gameMode == EditPosition) {
15196 	blackPlaysFirst = TRUE;
15197 	currentMove = 1;	/* kludge */
15198 	DisplayBothClocks();
15199 	currentMove = 0;
15200     } else if (gameMode == IcsExamining) {
15201         SendToICS(ics_prefix);
15202 	SendToICS("tomove black\n");
15203     }
15204 }
15205 
15206 void
EditPositionMenuEvent(ChessSquare selection,int x,int y)15207 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15208 {
15209     char buf[MSG_SIZ];
15210     ChessSquare piece = boards[0][y][x];
15211     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15212     static int lastVariant;
15213 
15214     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15215 
15216     switch (selection) {
15217       case ClearBoard:
15218 	fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15219 	MarkTargetSquares(1);
15220 	CopyBoard(currentBoard, boards[0]);
15221 	CopyBoard(menuBoard, initialPosition);
15222 	if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15223 	    SendToICS(ics_prefix);
15224 	    SendToICS("bsetup clear\n");
15225 	} else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15226 	    SendToICS(ics_prefix);
15227 	    SendToICS("clearboard\n");
15228 	} else {
15229             int nonEmpty = 0;
15230             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15231 		if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15232                 for (y = 0; y < BOARD_HEIGHT; y++) {
15233 		    if (gameMode == IcsExamining) {
15234 			if (boards[currentMove][y][x] != EmptySquare) {
15235 			  snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15236                                     AAA + x, ONE + y);
15237 			    SendToICS(buf);
15238 			}
15239 		    } else if(boards[0][y][x] != DarkSquare) {
15240 			if(boards[0][y][x] != p) nonEmpty++;
15241 			boards[0][y][x] = p;
15242 		    }
15243 		}
15244 	    }
15245 	    if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15246 		int r;
15247 		for(r = 0; r < BOARD_HEIGHT; r++) {
15248 		  for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates
15249 		    ChessSquare p = menuBoard[r][x];
15250 		    for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15251 		  }
15252 		}
15253 		DisplayMessage("Clicking clock again restores position", "");
15254 		if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15255 		if(!nonEmpty) { // asked to clear an empty board
15256 		    CopyBoard(boards[0], menuBoard);
15257 		} else
15258 		if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15259 		    CopyBoard(boards[0], initialPosition);
15260 		} else
15261 		if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15262 								 && !CompareBoards(nullBoard, erasedBoard)) {
15263 		    CopyBoard(boards[0], erasedBoard);
15264 		} else
15265 		    CopyBoard(erasedBoard, currentBoard);
15266 
15267 	    }
15268 	}
15269 	if (gameMode == EditPosition) {
15270 	    DrawPosition(FALSE, boards[0]);
15271 	}
15272 	break;
15273 
15274       case WhitePlay:
15275 	SetWhiteToPlayEvent();
15276 	break;
15277 
15278       case BlackPlay:
15279 	SetBlackToPlayEvent();
15280 	break;
15281 
15282       case EmptySquare:
15283 	if (gameMode == IcsExamining) {
15284             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15285             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15286 	    SendToICS(buf);
15287 	} else {
15288             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15289                 if(x == BOARD_LEFT-2) {
15290                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15291                     boards[0][y][1] = 0;
15292                 } else
15293                 if(x == BOARD_RGHT+1) {
15294                     if(y >= gameInfo.holdingsSize) break;
15295                     boards[0][y][BOARD_WIDTH-2] = 0;
15296                 } else break;
15297             }
15298 	    boards[0][y][x] = EmptySquare;
15299 	    DrawPosition(FALSE, boards[0]);
15300 	}
15301 	break;
15302 
15303       case PromotePiece:
15304         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15305            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15306             selection = (ChessSquare) (PROMOTED piece);
15307         } else if(piece == EmptySquare) selection = WhiteSilver;
15308         else selection = (ChessSquare)((int)piece - 1);
15309         goto defaultlabel;
15310 
15311       case DemotePiece:
15312         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15313            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15314             selection = (ChessSquare) (DEMOTED piece);
15315         } else if(piece == EmptySquare) selection = BlackSilver;
15316         else selection = (ChessSquare)((int)piece + 1);
15317         goto defaultlabel;
15318 
15319       case WhiteQueen:
15320       case BlackQueen:
15321         if(gameInfo.variant == VariantShatranj ||
15322            gameInfo.variant == VariantXiangqi  ||
15323            gameInfo.variant == VariantCourier  ||
15324            gameInfo.variant == VariantASEAN    ||
15325            gameInfo.variant == VariantMakruk     )
15326             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15327         goto defaultlabel;
15328 
15329       case WhiteKing:
15330       case BlackKing:
15331         if(gameInfo.variant == VariantXiangqi)
15332             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15333         if(gameInfo.variant == VariantKnightmate)
15334             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15335       default:
15336         defaultlabel:
15337 	if (gameMode == IcsExamining) {
15338             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15339 	    snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15340 		     PieceToChar(selection), AAA + x, ONE + y);
15341 	    SendToICS(buf);
15342 	} else {
15343             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15344                 int n;
15345                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15346                     n = PieceToNumber(selection - BlackPawn);
15347                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15348                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15349                     boards[0][BOARD_HEIGHT-1-n][1]++;
15350                 } else
15351                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15352                     n = PieceToNumber(selection);
15353                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15354                     boards[0][n][BOARD_WIDTH-1] = selection;
15355                     boards[0][n][BOARD_WIDTH-2]++;
15356                 }
15357             } else
15358 	    boards[0][y][x] = selection;
15359 	    DrawPosition(TRUE, boards[0]);
15360 	    ClearHighlights();
15361 	    fromX = fromY = -1;
15362 	}
15363 	break;
15364     }
15365 }
15366 
15367 
15368 void
DropMenuEvent(ChessSquare selection,int x,int y)15369 DropMenuEvent (ChessSquare selection, int x, int y)
15370 {
15371     ChessMove moveType;
15372 
15373     switch (gameMode) {
15374       case IcsPlayingWhite:
15375       case MachinePlaysBlack:
15376 	if (!WhiteOnMove(currentMove)) {
15377 	    DisplayMoveError(_("It is Black's turn"));
15378 	    return;
15379 	}
15380 	moveType = WhiteDrop;
15381 	break;
15382       case IcsPlayingBlack:
15383       case MachinePlaysWhite:
15384 	if (WhiteOnMove(currentMove)) {
15385 	    DisplayMoveError(_("It is White's turn"));
15386 	    return;
15387 	}
15388 	moveType = BlackDrop;
15389 	break;
15390       case EditGame:
15391 	moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15392 	break;
15393       default:
15394 	return;
15395     }
15396 
15397     if (moveType == BlackDrop && selection < BlackPawn) {
15398       selection = (ChessSquare) ((int) selection
15399 				 + (int) BlackPawn - (int) WhitePawn);
15400     }
15401     if (boards[currentMove][y][x] != EmptySquare) {
15402 	DisplayMoveError(_("That square is occupied"));
15403 	return;
15404     }
15405 
15406     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15407 }
15408 
15409 void
AcceptEvent()15410 AcceptEvent ()
15411 {
15412     /* Accept a pending offer of any kind from opponent */
15413 
15414     if (appData.icsActive) {
15415         SendToICS(ics_prefix);
15416 	SendToICS("accept\n");
15417     } else if (cmailMsgLoaded) {
15418 	if (currentMove == cmailOldMove &&
15419 	    commentList[cmailOldMove] != NULL &&
15420 	    StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15421 		   "Black offers a draw" : "White offers a draw")) {
15422 	    TruncateGame();
15423 	    GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15424 	    cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15425 	} else {
15426 	    DisplayError(_("There is no pending offer on this move"), 0);
15427 	    cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15428 	}
15429     } else {
15430 	/* Not used for offers from chess program */
15431     }
15432 }
15433 
15434 void
DeclineEvent()15435 DeclineEvent ()
15436 {
15437     /* Decline a pending offer of any kind from opponent */
15438 
15439     if (appData.icsActive) {
15440         SendToICS(ics_prefix);
15441 	SendToICS("decline\n");
15442     } else if (cmailMsgLoaded) {
15443 	if (currentMove == cmailOldMove &&
15444 	    commentList[cmailOldMove] != NULL &&
15445 	    StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15446 		   "Black offers a draw" : "White offers a draw")) {
15447 #ifdef NOTDEF
15448 	    AppendComment(cmailOldMove, "Draw declined", TRUE);
15449 	    DisplayComment(cmailOldMove - 1, "Draw declined");
15450 #endif /*NOTDEF*/
15451 	} else {
15452 	    DisplayError(_("There is no pending offer on this move"), 0);
15453 	}
15454     } else {
15455 	/* Not used for offers from chess program */
15456     }
15457 }
15458 
15459 void
RematchEvent()15460 RematchEvent ()
15461 {
15462     /* Issue ICS rematch command */
15463     if (appData.icsActive) {
15464         SendToICS(ics_prefix);
15465 	SendToICS("rematch\n");
15466     }
15467 }
15468 
15469 void
CallFlagEvent()15470 CallFlagEvent ()
15471 {
15472     /* Call your opponent's flag (claim a win on time) */
15473     if (appData.icsActive) {
15474         SendToICS(ics_prefix);
15475 	SendToICS("flag\n");
15476     } else {
15477 	switch (gameMode) {
15478 	  default:
15479 	    return;
15480 	  case MachinePlaysWhite:
15481 	    if (whiteFlag) {
15482 		if (blackFlag)
15483 		  GameEnds(GameIsDrawn, "Both players ran out of time",
15484 			   GE_PLAYER);
15485 		else
15486 		  GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15487 	    } else {
15488 		DisplayError(_("Your opponent is not out of time"), 0);
15489 	    }
15490 	    break;
15491 	  case MachinePlaysBlack:
15492 	    if (blackFlag) {
15493 		if (whiteFlag)
15494 		  GameEnds(GameIsDrawn, "Both players ran out of time",
15495 			   GE_PLAYER);
15496 		else
15497 		  GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15498 	    } else {
15499 		DisplayError(_("Your opponent is not out of time"), 0);
15500 	    }
15501 	    break;
15502 	}
15503     }
15504 }
15505 
15506 void
ClockClick(int which)15507 ClockClick (int which)
15508 {	// [HGM] code moved to back-end from winboard.c
15509 	if(which) { // black clock
15510 	  if (gameMode == EditPosition || gameMode == IcsExamining) {
15511 	    if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15512 	    SetBlackToPlayEvent();
15513 	  } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15514 		      gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15515           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15516 	  } else if (shiftKey) {
15517 	    AdjustClock(which, -1);
15518 	  } else if (gameMode == IcsPlayingWhite ||
15519 	             gameMode == MachinePlaysBlack) {
15520 	    CallFlagEvent();
15521 	  }
15522 	} else { // white clock
15523 	  if (gameMode == EditPosition || gameMode == IcsExamining) {
15524 	    if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15525 	    SetWhiteToPlayEvent();
15526 	  } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15527 		      gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15528           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15529 	  } else if (shiftKey) {
15530 	    AdjustClock(which, -1);
15531 	  } else if (gameMode == IcsPlayingBlack ||
15532 	           gameMode == MachinePlaysWhite) {
15533 	    CallFlagEvent();
15534 	  }
15535 	}
15536 }
15537 
15538 void
DrawEvent()15539 DrawEvent ()
15540 {
15541     /* Offer draw or accept pending draw offer from opponent */
15542 
15543     if (appData.icsActive) {
15544 	/* Note: tournament rules require draw offers to be
15545 	   made after you make your move but before you punch
15546 	   your clock.  Currently ICS doesn't let you do that;
15547 	   instead, you immediately punch your clock after making
15548 	   a move, but you can offer a draw at any time. */
15549 
15550         SendToICS(ics_prefix);
15551 	SendToICS("draw\n");
15552         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15553     } else if (cmailMsgLoaded) {
15554 	if (currentMove == cmailOldMove &&
15555 	    commentList[cmailOldMove] != NULL &&
15556 	    StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15557 		   "Black offers a draw" : "White offers a draw")) {
15558 	    GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15559 	    cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15560 	} else if (currentMove == cmailOldMove + 1) {
15561 	    char *offer = WhiteOnMove(cmailOldMove) ?
15562 	      "White offers a draw" : "Black offers a draw";
15563 	    AppendComment(currentMove, offer, TRUE);
15564 	    DisplayComment(currentMove - 1, offer);
15565 	    cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15566 	} else {
15567 	    DisplayError(_("You must make your move before offering a draw"), 0);
15568 	    cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15569 	}
15570     } else if (first.offeredDraw) {
15571 	GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15572     } else {
15573         if (first.sendDrawOffers) {
15574 	    SendToProgram("draw\n", &first);
15575             userOfferedDraw = TRUE;
15576 	}
15577     }
15578 }
15579 
15580 void
AdjournEvent()15581 AdjournEvent ()
15582 {
15583     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15584 
15585     if (appData.icsActive) {
15586         SendToICS(ics_prefix);
15587 	SendToICS("adjourn\n");
15588     } else {
15589 	/* Currently GNU Chess doesn't offer or accept Adjourns */
15590     }
15591 }
15592 
15593 
15594 void
AbortEvent()15595 AbortEvent ()
15596 {
15597     /* Offer Abort or accept pending Abort offer from opponent */
15598 
15599     if (appData.icsActive) {
15600         SendToICS(ics_prefix);
15601 	SendToICS("abort\n");
15602     } else {
15603 	GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15604     }
15605 }
15606 
15607 void
ResignEvent()15608 ResignEvent ()
15609 {
15610     /* Resign.  You can do this even if it's not your turn. */
15611 
15612     if (appData.icsActive) {
15613         SendToICS(ics_prefix);
15614 	SendToICS("resign\n");
15615     } else {
15616 	switch (gameMode) {
15617 	  case MachinePlaysWhite:
15618 	    GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15619 	    break;
15620 	  case MachinePlaysBlack:
15621 	    GameEnds(BlackWins, "White resigns", GE_PLAYER);
15622 	    break;
15623 	  case EditGame:
15624 	    if (cmailMsgLoaded) {
15625 		TruncateGame();
15626 		if (WhiteOnMove(cmailOldMove)) {
15627 		    GameEnds(BlackWins, "White resigns", GE_PLAYER);
15628 		} else {
15629 		    GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15630 		}
15631 		cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15632 	    }
15633 	    break;
15634 	  default:
15635 	    break;
15636 	}
15637     }
15638 }
15639 
15640 
15641 void
StopObservingEvent()15642 StopObservingEvent ()
15643 {
15644     /* Stop observing current games */
15645     SendToICS(ics_prefix);
15646     SendToICS("unobserve\n");
15647 }
15648 
15649 void
StopExaminingEvent()15650 StopExaminingEvent ()
15651 {
15652     /* Stop observing current game */
15653     SendToICS(ics_prefix);
15654     SendToICS("unexamine\n");
15655 }
15656 
15657 void
ForwardInner(int target)15658 ForwardInner (int target)
15659 {
15660     int limit; int oldSeekGraphUp = seekGraphUp;
15661 
15662     if (appData.debugMode)
15663 	fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15664 		target, currentMove, forwardMostMove);
15665 
15666     if (gameMode == EditPosition)
15667       return;
15668 
15669     seekGraphUp = FALSE;
15670     MarkTargetSquares(1);
15671     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15672 
15673     if (gameMode == PlayFromGameFile && !pausing)
15674       PauseEvent();
15675 
15676     if (gameMode == IcsExamining && pausing)
15677       limit = pauseExamForwardMostMove;
15678     else
15679       limit = forwardMostMove;
15680 
15681     if (target > limit) target = limit;
15682 
15683     if (target > 0 && moveList[target - 1][0]) {
15684 	int fromX, fromY, toX, toY;
15685         toX = moveList[target - 1][2] - AAA;
15686         toY = moveList[target - 1][3] - ONE;
15687 	if (moveList[target - 1][1] == '@') {
15688 	    if (appData.highlightLastMove) {
15689 		SetHighlights(-1, -1, toX, toY);
15690 	    }
15691 	} else {
15692             int viaX = moveList[target - 1][5] - AAA;
15693             int viaY = moveList[target - 1][6] - ONE;
15694             fromX = moveList[target - 1][0] - AAA;
15695             fromY = moveList[target - 1][1] - ONE;
15696 	    if (target == currentMove + 1) {
15697 		if(moveList[target - 1][4] == ';') { // multi-leg
15698 		    ChessSquare piece = boards[currentMove][viaY][viaX];
15699 		    AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15700 		    boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15701 		    AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15702 		    boards[currentMove][viaY][viaX] = piece;
15703 		} else
15704 		AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15705 	    }
15706 	    if (appData.highlightLastMove) {
15707 		SetHighlights(fromX, fromY, toX, toY);
15708 	    }
15709 	}
15710     }
15711     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15712 	gameMode == Training || gameMode == PlayFromGameFile ||
15713 	gameMode == AnalyzeFile) {
15714 	while (currentMove < target) {
15715 	    if(second.analyzing) SendMoveToProgram(currentMove, &second);
15716 	    SendMoveToProgram(currentMove++, &first);
15717 	}
15718     } else {
15719 	currentMove = target;
15720     }
15721 
15722     if (gameMode == EditGame || gameMode == EndOfGame) {
15723 	whiteTimeRemaining = timeRemaining[0][currentMove];
15724 	blackTimeRemaining = timeRemaining[1][currentMove];
15725     }
15726     DisplayBothClocks();
15727     DisplayMove(currentMove - 1);
15728     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15729     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15730     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15731 	DisplayComment(currentMove - 1, commentList[currentMove]);
15732     }
15733     ClearMap(); // [HGM] exclude: invalidate map
15734 }
15735 
15736 
15737 void
ForwardEvent()15738 ForwardEvent ()
15739 {
15740     if (gameMode == IcsExamining && !pausing) {
15741         SendToICS(ics_prefix);
15742 	SendToICS("forward\n");
15743     } else {
15744 	ForwardInner(currentMove + 1);
15745     }
15746 }
15747 
15748 void
ToEndEvent()15749 ToEndEvent ()
15750 {
15751     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15752 	/* to optimze, we temporarily turn off analysis mode while we feed
15753 	 * the remaining moves to the engine. Otherwise we get analysis output
15754 	 * after each move.
15755 	 */
15756         if (first.analysisSupport) {
15757 	  SendToProgram("exit\nforce\n", &first);
15758 	  first.analyzing = FALSE;
15759 	}
15760     }
15761 
15762     if (gameMode == IcsExamining && !pausing) {
15763         SendToICS(ics_prefix);
15764 	SendToICS("forward 999999\n");
15765     } else {
15766 	ForwardInner(forwardMostMove);
15767     }
15768 
15769     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15770 	/* we have fed all the moves, so reactivate analysis mode */
15771 	SendToProgram("analyze\n", &first);
15772 	first.analyzing = TRUE;
15773 	/*first.maybeThinking = TRUE;*/
15774 	first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15775     }
15776 }
15777 
15778 void
BackwardInner(int target)15779 BackwardInner (int target)
15780 {
15781     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15782 
15783     if (appData.debugMode)
15784 	fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15785 		target, currentMove, forwardMostMove);
15786 
15787     if (gameMode == EditPosition) return;
15788     seekGraphUp = FALSE;
15789     MarkTargetSquares(1);
15790     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15791     if (currentMove <= backwardMostMove) {
15792 	ClearHighlights();
15793 	DrawPosition(full_redraw, boards[currentMove]);
15794 	return;
15795     }
15796     if (gameMode == PlayFromGameFile && !pausing)
15797       PauseEvent();
15798 
15799     if (moveList[target][0]) {
15800 	int fromX, fromY, toX, toY;
15801         toX = moveList[target][2] - AAA;
15802         toY = moveList[target][3] - ONE;
15803 	if (moveList[target][1] == '@') {
15804 	    if (appData.highlightLastMove) {
15805 		SetHighlights(-1, -1, toX, toY);
15806 	    }
15807 	} else {
15808             fromX = moveList[target][0] - AAA;
15809             fromY = moveList[target][1] - ONE;
15810 	    if (target == currentMove - 1) {
15811 		AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15812 	    }
15813 	    if (appData.highlightLastMove) {
15814 		SetHighlights(fromX, fromY, toX, toY);
15815 	    }
15816 	}
15817     }
15818     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15819 	gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15820 	while (currentMove > target) {
15821 	    if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15822 		// null move cannot be undone. Reload program with move history before it.
15823 		int i;
15824 		for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15825 		    if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15826 		}
15827 		SendBoard(&first, i);
15828 	      if(second.analyzing) SendBoard(&second, i);
15829 		for(currentMove=i; currentMove<target; currentMove++) {
15830 		    SendMoveToProgram(currentMove, &first);
15831 		    if(second.analyzing) SendMoveToProgram(currentMove, &second);
15832 		}
15833 		break;
15834 	    }
15835 	    SendToBoth("undo\n");
15836 	    currentMove--;
15837 	}
15838     } else {
15839 	currentMove = target;
15840     }
15841 
15842     if (gameMode == EditGame || gameMode == EndOfGame) {
15843 	whiteTimeRemaining = timeRemaining[0][currentMove];
15844 	blackTimeRemaining = timeRemaining[1][currentMove];
15845     }
15846     DisplayBothClocks();
15847     DisplayMove(currentMove - 1);
15848     DrawPosition(full_redraw, boards[currentMove]);
15849     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15850     // [HGM] PV info: routine tests if comment empty
15851     DisplayComment(currentMove - 1, commentList[currentMove]);
15852     ClearMap(); // [HGM] exclude: invalidate map
15853 }
15854 
15855 void
BackwardEvent()15856 BackwardEvent ()
15857 {
15858     if (gameMode == IcsExamining && !pausing) {
15859         SendToICS(ics_prefix);
15860 	SendToICS("backward\n");
15861     } else {
15862 	BackwardInner(currentMove - 1);
15863     }
15864 }
15865 
15866 void
ToStartEvent()15867 ToStartEvent ()
15868 {
15869     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15870 	/* to optimize, we temporarily turn off analysis mode while we undo
15871 	 * all the moves. Otherwise we get analysis output after each undo.
15872 	 */
15873         if (first.analysisSupport) {
15874 	  SendToProgram("exit\nforce\n", &first);
15875 	  first.analyzing = FALSE;
15876 	}
15877     }
15878 
15879     if (gameMode == IcsExamining && !pausing) {
15880         SendToICS(ics_prefix);
15881 	SendToICS("backward 999999\n");
15882     } else {
15883 	BackwardInner(backwardMostMove);
15884     }
15885 
15886     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15887 	/* we have fed all the moves, so reactivate analysis mode */
15888 	SendToProgram("analyze\n", &first);
15889 	first.analyzing = TRUE;
15890 	/*first.maybeThinking = TRUE;*/
15891 	first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15892     }
15893 }
15894 
15895 void
ToNrEvent(int to)15896 ToNrEvent (int to)
15897 {
15898   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15899   if (to >= forwardMostMove) to = forwardMostMove;
15900   if (to <= backwardMostMove) to = backwardMostMove;
15901   if (to < currentMove) {
15902     BackwardInner(to);
15903   } else {
15904     ForwardInner(to);
15905   }
15906 }
15907 
15908 void
RevertEvent(Boolean annotate)15909 RevertEvent (Boolean annotate)
15910 {
15911     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15912 	return;
15913     }
15914     if (gameMode != IcsExamining) {
15915 	DisplayError(_("You are not examining a game"), 0);
15916 	return;
15917     }
15918     if (pausing) {
15919 	DisplayError(_("You can't revert while pausing"), 0);
15920 	return;
15921     }
15922     SendToICS(ics_prefix);
15923     SendToICS("revert\n");
15924 }
15925 
15926 void
RetractMoveEvent()15927 RetractMoveEvent ()
15928 {
15929     switch (gameMode) {
15930       case MachinePlaysWhite:
15931       case MachinePlaysBlack:
15932 	if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15933 	    DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15934 	    return;
15935 	}
15936 	if (forwardMostMove < 2) return;
15937 	currentMove = forwardMostMove = forwardMostMove - 2;
15938 	whiteTimeRemaining = timeRemaining[0][currentMove];
15939 	blackTimeRemaining = timeRemaining[1][currentMove];
15940 	DisplayBothClocks();
15941 	DisplayMove(currentMove - 1);
15942 	ClearHighlights();/*!! could figure this out*/
15943 	DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15944 	SendToProgram("remove\n", &first);
15945 	/*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15946 	break;
15947 
15948       case BeginningOfGame:
15949       default:
15950 	break;
15951 
15952       case IcsPlayingWhite:
15953       case IcsPlayingBlack:
15954 	if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15955 	    SendToICS(ics_prefix);
15956 	    SendToICS("takeback 2\n");
15957 	} else {
15958 	    SendToICS(ics_prefix);
15959 	    SendToICS("takeback 1\n");
15960 	}
15961 	break;
15962     }
15963 }
15964 
15965 void
MoveNowEvent()15966 MoveNowEvent ()
15967 {
15968     ChessProgramState *cps;
15969 
15970     switch (gameMode) {
15971       case MachinePlaysWhite:
15972 	if (!WhiteOnMove(forwardMostMove)) {
15973 	    DisplayError(_("It is your turn"), 0);
15974 	    return;
15975 	}
15976 	cps = &first;
15977 	break;
15978       case MachinePlaysBlack:
15979 	if (WhiteOnMove(forwardMostMove)) {
15980 	    DisplayError(_("It is your turn"), 0);
15981 	    return;
15982 	}
15983 	cps = &first;
15984 	break;
15985       case TwoMachinesPlay:
15986 	if (WhiteOnMove(forwardMostMove) ==
15987 	    (first.twoMachinesColor[0] == 'w')) {
15988 	    cps = &first;
15989 	} else {
15990 	    cps = &second;
15991 	}
15992 	break;
15993       case BeginningOfGame:
15994       default:
15995 	return;
15996     }
15997     SendToProgram("?\n", cps);
15998 }
15999 
16000 void
TruncateGameEvent()16001 TruncateGameEvent ()
16002 {
16003     EditGameEvent();
16004     if (gameMode != EditGame) return;
16005     TruncateGame();
16006 }
16007 
16008 void
TruncateGame()16009 TruncateGame ()
16010 {
16011     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16012     if (forwardMostMove > currentMove) {
16013 	if (gameInfo.resultDetails != NULL) {
16014 	    free(gameInfo.resultDetails);
16015 	    gameInfo.resultDetails = NULL;
16016 	    gameInfo.result = GameUnfinished;
16017 	}
16018 	forwardMostMove = currentMove;
16019 	HistorySet(parseList, backwardMostMove, forwardMostMove,
16020 		   currentMove-1);
16021     }
16022 }
16023 
16024 void
HintEvent()16025 HintEvent ()
16026 {
16027     if (appData.noChessProgram) return;
16028     switch (gameMode) {
16029       case MachinePlaysWhite:
16030 	if (WhiteOnMove(forwardMostMove)) {
16031 	    DisplayError(_("Wait until your turn."), 0);
16032 	    return;
16033 	}
16034 	break;
16035       case BeginningOfGame:
16036       case MachinePlaysBlack:
16037 	if (!WhiteOnMove(forwardMostMove)) {
16038 	    DisplayError(_("Wait until your turn."), 0);
16039 	    return;
16040 	}
16041 	break;
16042       default:
16043 	DisplayError(_("No hint available"), 0);
16044 	return;
16045     }
16046     SendToProgram("hint\n", &first);
16047     hintRequested = TRUE;
16048 }
16049 
16050 int
SaveSelected(FILE * g,int dummy,char * dummy2)16051 SaveSelected (FILE *g, int dummy, char *dummy2)
16052 {
16053     ListGame * lg = (ListGame *) gameList.head;
16054     int nItem, cnt=0;
16055     FILE *f;
16056 
16057     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16058         DisplayError(_("Game list not loaded or empty"), 0);
16059         return 0;
16060     }
16061 
16062     creatingBook = TRUE; // suppresses stuff during load game
16063 
16064     /* Get list size */
16065     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16066 	if(lg->position >= 0) { // selected?
16067 	    LoadGame(f, nItem, "", TRUE);
16068 	    SaveGamePGN2(g); // leaves g open
16069 	    cnt++; DoEvents();
16070 	}
16071         lg = (ListGame *) lg->node.succ;
16072     }
16073 
16074     fclose(g);
16075     creatingBook = FALSE;
16076 
16077     return cnt;
16078 }
16079 
16080 void
CreateBookEvent()16081 CreateBookEvent ()
16082 {
16083     ListGame * lg = (ListGame *) gameList.head;
16084     FILE *f, *g;
16085     int nItem;
16086     static int secondTime = FALSE;
16087 
16088     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16089         DisplayError(_("Game list not loaded or empty"), 0);
16090         return;
16091     }
16092 
16093     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16094         fclose(g);
16095 	secondTime++;
16096 	DisplayNote(_("Book file exists! Try again for overwrite."));
16097 	return;
16098     }
16099 
16100     creatingBook = TRUE;
16101     secondTime = FALSE;
16102 
16103     /* Get list size */
16104     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16105 	if(lg->position >= 0) {
16106 	    LoadGame(f, nItem, "", TRUE);
16107 	    AddGameToBook(TRUE);
16108 	    DoEvents();
16109 	}
16110         lg = (ListGame *) lg->node.succ;
16111     }
16112 
16113     creatingBook = FALSE;
16114     FlushBook();
16115 }
16116 
16117 void
BookEvent()16118 BookEvent ()
16119 {
16120     if (appData.noChessProgram) return;
16121     switch (gameMode) {
16122       case MachinePlaysWhite:
16123 	if (WhiteOnMove(forwardMostMove)) {
16124 	    DisplayError(_("Wait until your turn."), 0);
16125 	    return;
16126 	}
16127 	break;
16128       case BeginningOfGame:
16129       case MachinePlaysBlack:
16130 	if (!WhiteOnMove(forwardMostMove)) {
16131 	    DisplayError(_("Wait until your turn."), 0);
16132 	    return;
16133 	}
16134 	break;
16135       case EditPosition:
16136 	EditPositionDone(TRUE);
16137 	break;
16138       case TwoMachinesPlay:
16139 	return;
16140       default:
16141 	break;
16142     }
16143     SendToProgram("bk\n", &first);
16144     bookOutput[0] = NULLCHAR;
16145     bookRequested = TRUE;
16146 }
16147 
16148 void
AboutGameEvent()16149 AboutGameEvent ()
16150 {
16151     char *tags = PGNTags(&gameInfo);
16152     TagsPopUp(tags, CmailMsg());
16153     free(tags);
16154 }
16155 
16156 /* end button procedures */
16157 
16158 void
PrintPosition(FILE * fp,int move)16159 PrintPosition (FILE *fp, int move)
16160 {
16161     int i, j;
16162 
16163     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16164         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16165 	    char c = PieceToChar(boards[move][i][j]);
16166 	    fputc(c == 'x' ? '.' : c, fp);
16167             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16168 	}
16169     }
16170     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16171       fprintf(fp, "white to play\n");
16172     else
16173       fprintf(fp, "black to play\n");
16174 }
16175 
16176 void
PrintOpponents(FILE * fp)16177 PrintOpponents (FILE *fp)
16178 {
16179     if (gameInfo.white != NULL) {
16180 	fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16181     } else {
16182 	fprintf(fp, "\n");
16183     }
16184 }
16185 
16186 /* Find last component of program's own name, using some heuristics */
16187 void
TidyProgramName(char * prog,char * host,char buf[MSG_SIZ])16188 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16189 {
16190     char *p, *q, c;
16191     int local = (strcmp(host, "localhost") == 0);
16192     while (!local && (p = strchr(prog, ';')) != NULL) {
16193 	p++;
16194 	while (*p == ' ') p++;
16195 	prog = p;
16196     }
16197     if (*prog == '"' || *prog == '\'') {
16198 	q = strchr(prog + 1, *prog);
16199     } else {
16200 	q = strchr(prog, ' ');
16201     }
16202     if (q == NULL) q = prog + strlen(prog);
16203     p = q;
16204     while (p >= prog && *p != '/' && *p != '\\') p--;
16205     p++;
16206     if(p == prog && *p == '"') p++;
16207     c = *q; *q = 0;
16208     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16209     memcpy(buf, p, q - p);
16210     buf[q - p] = NULLCHAR;
16211     if (!local) {
16212 	strcat(buf, "@");
16213 	strcat(buf, host);
16214     }
16215 }
16216 
16217 char *
TimeControlTagValue()16218 TimeControlTagValue ()
16219 {
16220     char buf[MSG_SIZ];
16221     if (!appData.clockMode) {
16222       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16223     } else if (movesPerSession > 0) {
16224       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16225     } else if (timeIncrement == 0) {
16226       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16227     } else {
16228       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16229     }
16230     return StrSave(buf);
16231 }
16232 
16233 void
SetGameInfo()16234 SetGameInfo ()
16235 {
16236     /* This routine is used only for certain modes */
16237     VariantClass v = gameInfo.variant;
16238     ChessMove r = GameUnfinished;
16239     char *p = NULL;
16240 
16241     if(keepInfo) return;
16242 
16243     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16244 	r = gameInfo.result;
16245 	p = gameInfo.resultDetails;
16246 	gameInfo.resultDetails = NULL;
16247     }
16248     ClearGameInfo(&gameInfo);
16249     gameInfo.variant = v;
16250 
16251     switch (gameMode) {
16252       case MachinePlaysWhite:
16253 	gameInfo.event = StrSave( appData.pgnEventHeader );
16254 	gameInfo.site = StrSave(HostName());
16255 	gameInfo.date = PGNDate();
16256 	gameInfo.round = StrSave("-");
16257 	gameInfo.white = StrSave(first.tidy);
16258 	gameInfo.black = StrSave(UserName());
16259 	gameInfo.timeControl = TimeControlTagValue();
16260 	break;
16261 
16262       case MachinePlaysBlack:
16263 	gameInfo.event = StrSave( appData.pgnEventHeader );
16264 	gameInfo.site = StrSave(HostName());
16265 	gameInfo.date = PGNDate();
16266 	gameInfo.round = StrSave("-");
16267 	gameInfo.white = StrSave(UserName());
16268 	gameInfo.black = StrSave(first.tidy);
16269 	gameInfo.timeControl = TimeControlTagValue();
16270 	break;
16271 
16272       case TwoMachinesPlay:
16273 	gameInfo.event = StrSave( appData.pgnEventHeader );
16274 	gameInfo.site = StrSave(HostName());
16275 	gameInfo.date = PGNDate();
16276 	if (roundNr > 0) {
16277 	    char buf[MSG_SIZ];
16278 	    snprintf(buf, MSG_SIZ, "%d", roundNr);
16279 	    gameInfo.round = StrSave(buf);
16280 	} else {
16281 	    gameInfo.round = StrSave("-");
16282 	}
16283 	if (first.twoMachinesColor[0] == 'w') {
16284 	    gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16285 	    gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16286 	} else {
16287 	    gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16288 	    gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16289 	}
16290 	gameInfo.timeControl = TimeControlTagValue();
16291 	break;
16292 
16293       case EditGame:
16294 	gameInfo.event = StrSave("Edited game");
16295 	gameInfo.site = StrSave(HostName());
16296 	gameInfo.date = PGNDate();
16297 	gameInfo.round = StrSave("-");
16298 	gameInfo.white = StrSave("-");
16299 	gameInfo.black = StrSave("-");
16300 	gameInfo.result = r;
16301 	gameInfo.resultDetails = p;
16302 	break;
16303 
16304       case EditPosition:
16305 	gameInfo.event = StrSave("Edited position");
16306 	gameInfo.site = StrSave(HostName());
16307 	gameInfo.date = PGNDate();
16308 	gameInfo.round = StrSave("-");
16309 	gameInfo.white = StrSave("-");
16310 	gameInfo.black = StrSave("-");
16311 	break;
16312 
16313       case IcsPlayingWhite:
16314       case IcsPlayingBlack:
16315       case IcsObserving:
16316       case IcsExamining:
16317 	break;
16318 
16319       case PlayFromGameFile:
16320 	gameInfo.event = StrSave("Game from non-PGN file");
16321 	gameInfo.site = StrSave(HostName());
16322 	gameInfo.date = PGNDate();
16323 	gameInfo.round = StrSave("-");
16324 	gameInfo.white = StrSave("?");
16325 	gameInfo.black = StrSave("?");
16326 	break;
16327 
16328       default:
16329 	break;
16330     }
16331 }
16332 
16333 void
ReplaceComment(int index,char * text)16334 ReplaceComment (int index, char *text)
16335 {
16336     int len;
16337     char *p;
16338     float score;
16339 
16340     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16341        pvInfoList[index-1].depth == len &&
16342        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16343        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16344     while (*text == '\n') text++;
16345     len = strlen(text);
16346     while (len > 0 && text[len - 1] == '\n') len--;
16347 
16348     if (commentList[index] != NULL)
16349       free(commentList[index]);
16350 
16351     if (len == 0) {
16352 	commentList[index] = NULL;
16353 	return;
16354     }
16355   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16356       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16357       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16358     commentList[index] = (char *) malloc(len + 2);
16359     strncpy(commentList[index], text, len);
16360     commentList[index][len] = '\n';
16361     commentList[index][len + 1] = NULLCHAR;
16362   } else {
16363     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16364     char *p;
16365     commentList[index] = (char *) malloc(len + 7);
16366     safeStrCpy(commentList[index], "{\n", 3);
16367     safeStrCpy(commentList[index]+2, text, len+1);
16368     commentList[index][len+2] = NULLCHAR;
16369     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16370     strcat(commentList[index], "\n}\n");
16371   }
16372 }
16373 
16374 void
CrushCRs(char * text)16375 CrushCRs (char *text)
16376 {
16377   char *p = text;
16378   char *q = text;
16379   char ch;
16380 
16381   do {
16382     ch = *p++;
16383     if (ch == '\r') continue;
16384     *q++ = ch;
16385   } while (ch != '\0');
16386 }
16387 
16388 void
AppendComment(int index,char * text,Boolean addBraces)16389 AppendComment (int index, char *text, Boolean addBraces)
16390 /* addBraces  tells if we should add {} */
16391 {
16392     int oldlen, len;
16393     char *old;
16394 
16395 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16396     if(addBraces == 3) addBraces = 0; else // force appending literally
16397     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16398 
16399     CrushCRs(text);
16400     while (*text == '\n') text++;
16401     len = strlen(text);
16402     while (len > 0 && text[len - 1] == '\n') len--;
16403     text[len] = NULLCHAR;
16404 
16405     if (len == 0) return;
16406 
16407     if (commentList[index] != NULL) {
16408       Boolean addClosingBrace = addBraces;
16409 	old = commentList[index];
16410 	oldlen = strlen(old);
16411 	while(commentList[index][oldlen-1] ==  '\n')
16412 	  commentList[index][--oldlen] = NULLCHAR;
16413 	commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16414 	safeStrCpy(commentList[index], old, oldlen + len + 6);
16415 	free(old);
16416 	// [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16417 	if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16418 	  if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16419 	  while (*text == '\n') { text++; len--; }
16420 	  commentList[index][--oldlen] = NULLCHAR;
16421       }
16422 	if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16423 	else          strcat(commentList[index], "\n");
16424 	strcat(commentList[index], text);
16425 	if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16426 	else          strcat(commentList[index], "\n");
16427     } else {
16428 	commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16429 	if(addBraces)
16430 	  safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16431 	else commentList[index][0] = NULLCHAR;
16432 	strcat(commentList[index], text);
16433 	strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16434 	if(addBraces == TRUE) strcat(commentList[index], "}\n");
16435     }
16436 }
16437 
16438 static char *
FindStr(char * text,char * sub_text)16439 FindStr (char * text, char * sub_text)
16440 {
16441     char * result = strstr( text, sub_text );
16442 
16443     if( result != NULL ) {
16444         result += strlen( sub_text );
16445     }
16446 
16447     return result;
16448 }
16449 
16450 /* [AS] Try to extract PV info from PGN comment */
16451 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16452 char *
GetInfoFromComment(int index,char * text)16453 GetInfoFromComment (int index, char * text)
16454 {
16455     char * sep = text, *p;
16456 
16457     if( text != NULL && index > 0 ) {
16458         int score = 0;
16459         int depth = 0;
16460         int time = -1, sec = 0, deci;
16461         char * s_eval = FindStr( text, "[%eval " );
16462         char * s_emt = FindStr( text, "[%emt " );
16463 #if 0
16464         if( s_eval != NULL || s_emt != NULL ) {
16465 #else
16466         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16467 #endif
16468             /* New style */
16469             char delim;
16470 
16471             if( s_eval != NULL ) {
16472                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16473                     return text;
16474                 }
16475 
16476                 if( delim != ']' ) {
16477                     return text;
16478                 }
16479             }
16480 
16481             if( s_emt != NULL ) {
16482             }
16483 		return text;
16484         }
16485         else {
16486             /* We expect something like: [+|-]nnn.nn/dd */
16487             int score_lo = 0;
16488 
16489             if(*text != '{') return text; // [HGM] braces: must be normal comment
16490 
16491             sep = strchr( text, '/' );
16492             if( sep == NULL || sep < (text+4) ) {
16493                 return text;
16494             }
16495 
16496             p = text;
16497             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16498             if(p[1] == '(') { // comment starts with PV
16499                p = strchr(p, ')'); // locate end of PV
16500                if(p == NULL || sep < p+5) return text;
16501                // at this point we have something like "{(.*) +0.23/6 ..."
16502                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16503                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16504                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16505             }
16506             time = -1; sec = -1; deci = -1;
16507             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16508 		sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16509                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16510                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16511                 return text;
16512             }
16513 
16514             if( score_lo < 0 || score_lo >= 100 ) {
16515                 return text;
16516             }
16517 
16518             if(sec >= 0) time = 600*time + 10*sec; else
16519             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16520 
16521             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16522 
16523             /* [HGM] PV time: now locate end of PV info */
16524             while( *++sep >= '0' && *sep <= '9'); // strip depth
16525             if(time >= 0)
16526             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16527             if(sec >= 0)
16528             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16529             if(deci >= 0)
16530             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16531             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16532         }
16533 
16534         if( depth <= 0 ) {
16535             return text;
16536         }
16537 
16538         if( time < 0 ) {
16539             time = -1;
16540         }
16541 
16542         pvInfoList[index-1].depth = depth;
16543         pvInfoList[index-1].score = score;
16544         pvInfoList[index-1].time  = 10*time; // centi-sec
16545         if(*sep == '}') *sep = 0; else *--sep = '{';
16546         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16547     }
16548     return sep;
16549 }
16550 
16551 void
16552 SendToProgram (char *message, ChessProgramState *cps)
16553 {
16554     int count, outCount, error;
16555     char buf[MSG_SIZ];
16556 
16557     if (cps->pr == NoProc) return;
16558     Attention(cps);
16559 
16560     if (appData.debugMode) {
16561 	TimeMark now;
16562 	GetTimeMark(&now);
16563 	fprintf(debugFP, "%ld >%-6s: %s",
16564 		SubtractTimeMarks(&now, &programStartTime),
16565 		cps->which, message);
16566 	if(serverFP)
16567 	    fprintf(serverFP, "%ld >%-6s: %s",
16568 		SubtractTimeMarks(&now, &programStartTime),
16569 		cps->which, message), fflush(serverFP);
16570     }
16571 
16572     count = strlen(message);
16573     outCount = OutputToProcess(cps->pr, message, count, &error);
16574     if (outCount < count && !exiting
16575                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16576       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16577       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16578         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16579             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16580                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16581 		if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16582                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16583             } else {
16584                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16585 		if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16586                 gameInfo.result = res;
16587             }
16588             gameInfo.resultDetails = StrSave(buf);
16589         }
16590 	if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16591         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16592     }
16593 }
16594 
16595 void
16596 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16597 {
16598     char *end_str;
16599     char buf[MSG_SIZ];
16600     ChessProgramState *cps = (ChessProgramState *)closure;
16601 
16602     if (isr != cps->isr) return; /* Killed intentionally */
16603     if (count <= 0) {
16604 	if (count == 0) {
16605 	    RemoveInputSource(cps->isr);
16606 	    snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16607 		    _(cps->which), cps->program);
16608 	    if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16609 	    if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16610                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16611                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16612 		    if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16613                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16614                 } else {
16615                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16616 		    if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16617                     gameInfo.result = res;
16618                 }
16619                 gameInfo.resultDetails = StrSave(buf);
16620             }
16621 	    if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16622 	    if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16623 	} else {
16624 	    snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16625 		    _(cps->which), cps->program);
16626 	    RemoveInputSource(cps->isr);
16627 
16628             /* [AS] Program is misbehaving badly... kill it */
16629             if( count == -2 ) {
16630                 DestroyChildProcess( cps->pr, 9 );
16631                 cps->pr = NoProc;
16632             }
16633 
16634             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16635 	}
16636 	return;
16637     }
16638 
16639     if ((end_str = strchr(message, '\r')) != NULL)
16640       *end_str = NULLCHAR;
16641     if ((end_str = strchr(message, '\n')) != NULL)
16642       *end_str = NULLCHAR;
16643 
16644     if (appData.debugMode) {
16645 	TimeMark now; int print = 1;
16646 	char *quote = ""; char c; int i;
16647 
16648 	if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16649 		char start = message[0];
16650 		if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16651 		if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16652 		   sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16653 		   sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16654 		   sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16655 		   sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16656 		   sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16657 		   sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16658 		   sscanf(message, "hint: %c", &c)!=1 &&
16659 		   sscanf(message, "pong %c", &c)!=1   && start != '#')	{
16660 		    quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16661 		    print = (appData.engineComments >= 2);
16662 		}
16663 		message[0] = start; // restore original message
16664 	}
16665 	if(print) {
16666 		GetTimeMark(&now);
16667 		fprintf(debugFP, "%ld <%-6s: %s%s\n",
16668 			SubtractTimeMarks(&now, &programStartTime), cps->which,
16669 			quote,
16670 			message);
16671 		if(serverFP)
16672 		    fprintf(serverFP, "%ld <%-6s: %s%s\n",
16673 			SubtractTimeMarks(&now, &programStartTime), cps->which,
16674 			quote,
16675 			message), fflush(serverFP);
16676 	}
16677     }
16678 
16679     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16680     if (appData.icsEngineAnalyze) {
16681         if (strstr(message, "whisper") != NULL ||
16682              strstr(message, "kibitz") != NULL ||
16683             strstr(message, "tellics") != NULL) return;
16684     }
16685 
16686     HandleMachineMove(message, cps);
16687 }
16688 
16689 
16690 void
16691 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16692 {
16693     char buf[MSG_SIZ];
16694     int seconds;
16695 
16696     if( timeControl_2 > 0 ) {
16697         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16698             tc = timeControl_2;
16699         }
16700     }
16701     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16702     inc /= cps->timeOdds;
16703     st  /= cps->timeOdds;
16704 
16705     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16706 
16707     if (st > 0) {
16708       /* Set exact time per move, normally using st command */
16709       if (cps->stKludge) {
16710 	/* GNU Chess 4 has no st command; uses level in a nonstandard way */
16711 	seconds = st % 60;
16712 	if (seconds == 0) {
16713 	  snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16714 	} else {
16715 	  snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16716 	}
16717       } else {
16718 	snprintf(buf, MSG_SIZ, "st %d\n", st);
16719       }
16720     } else {
16721       /* Set conventional or incremental time control, using level command */
16722       if (seconds == 0) {
16723 	/* Note old gnuchess bug -- minutes:seconds used to not work.
16724 	   Fixed in later versions, but still avoid :seconds
16725 	   when seconds is 0. */
16726 	snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16727       } else {
16728 	snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16729 		 seconds, inc/1000.);
16730       }
16731     }
16732     SendToProgram(buf, cps);
16733 
16734     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16735     /* Orthogonally, limit search to given depth */
16736     if (sd > 0) {
16737       if (cps->sdKludge) {
16738 	snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16739       } else {
16740 	snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16741       }
16742       SendToProgram(buf, cps);
16743     }
16744 
16745     if(cps->nps >= 0) { /* [HGM] nps */
16746 	if(cps->supportsNPS == FALSE)
16747 	  cps->nps = -1; // don't use if engine explicitly says not supported!
16748 	else {
16749 	  snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16750 	  SendToProgram(buf, cps);
16751 	}
16752     }
16753 }
16754 
16755 ChessProgramState *
16756 WhitePlayer ()
16757 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16758 {
16759     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16760        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16761         return &second;
16762     return &first;
16763 }
16764 
16765 void
16766 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16767 {
16768     char message[MSG_SIZ];
16769     long time, otime;
16770 
16771     /* Note: this routine must be called when the clocks are stopped
16772        or when they have *just* been set or switched; otherwise
16773        it will be off by the time since the current tick started.
16774     */
16775     if (machineWhite) {
16776 	time = whiteTimeRemaining / 10;
16777 	otime = blackTimeRemaining / 10;
16778     } else {
16779 	time = blackTimeRemaining / 10;
16780 	otime = whiteTimeRemaining / 10;
16781     }
16782     /* [HGM] translate opponent's time by time-odds factor */
16783     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16784 
16785     if (time <= 0) time = 1;
16786     if (otime <= 0) otime = 1;
16787 
16788     snprintf(message, MSG_SIZ, "time %ld\n", time);
16789     SendToProgram(message, cps);
16790 
16791     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16792     SendToProgram(message, cps);
16793 }
16794 
16795 char *
16796 EngineDefinedVariant (ChessProgramState *cps, int n)
16797 {   // return name of n-th unknown variant that engine supports
16798     static char buf[MSG_SIZ];
16799     char *p, *s = cps->variants;
16800     if(!s) return NULL;
16801     do { // parse string from variants feature
16802       VariantClass v;
16803 	p = strchr(s, ',');
16804 	if(p) *p = NULLCHAR;
16805       v = StringToVariant(s);
16806       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16807 	if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16808 	    if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16809 	       !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16810 	       !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16811 	       !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16812 	    if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16813 	}
16814 	if(p) *p++ = ',';
16815 	if(n < 0) return buf;
16816     } while(s = p);
16817     return NULL;
16818 }
16819 
16820 int
16821 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16822 {
16823   char buf[MSG_SIZ];
16824   int len = strlen(name);
16825   int val;
16826 
16827   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16828     (*p) += len + 1;
16829     sscanf(*p, "%d", &val);
16830     *loc = (val != 0);
16831     while (**p && **p != ' ')
16832       (*p)++;
16833     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16834     SendToProgram(buf, cps);
16835     return TRUE;
16836   }
16837   return FALSE;
16838 }
16839 
16840 int
16841 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16842 {
16843   char buf[MSG_SIZ];
16844   int len = strlen(name);
16845   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16846     (*p) += len + 1;
16847     sscanf(*p, "%d", loc);
16848     while (**p && **p != ' ') (*p)++;
16849     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16850     SendToProgram(buf, cps);
16851     return TRUE;
16852   }
16853   return FALSE;
16854 }
16855 
16856 int
16857 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16858 {
16859   char buf[MSG_SIZ];
16860   int len = strlen(name);
16861   if (strncmp((*p), name, len) == 0
16862       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16863     (*p) += len + 2;
16864     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16865     sscanf(*p, "%[^\"]", *loc);
16866     while (**p && **p != '\"') (*p)++;
16867     if (**p == '\"') (*p)++;
16868     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16869     SendToProgram(buf, cps);
16870     return TRUE;
16871   }
16872   return FALSE;
16873 }
16874 
16875 int
16876 ParseOption (Option *opt, ChessProgramState *cps)
16877 // [HGM] options: process the string that defines an engine option, and determine
16878 // name, type, default value, and allowed value range
16879 {
16880 	char *p, *q, buf[MSG_SIZ];
16881 	int n, min = (-1)<<31, max = 1<<31, def;
16882 
16883 	if(p = strstr(opt->name, " -spin ")) {
16884 	    if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16885 	    if(max < min) max = min; // enforce consistency
16886 	    if(def < min) def = min;
16887 	    if(def > max) def = max;
16888 	    opt->value = def;
16889 	    opt->min = min;
16890 	    opt->max = max;
16891 	    opt->type = Spin;
16892 	} else if((p = strstr(opt->name, " -slider "))) {
16893 	    // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16894 	    if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16895 	    if(max < min) max = min; // enforce consistency
16896 	    if(def < min) def = min;
16897 	    if(def > max) def = max;
16898 	    opt->value = def;
16899 	    opt->min = min;
16900 	    opt->max = max;
16901 	    opt->type = Spin; // Slider;
16902 	} else if((p = strstr(opt->name, " -string "))) {
16903 	    opt->textValue = p+9;
16904 	    opt->type = TextBox;
16905 	} else if((p = strstr(opt->name, " -file "))) {
16906 	    // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16907 	    opt->textValue = p+7;
16908 	    opt->type = FileName; // FileName;
16909 	} else if((p = strstr(opt->name, " -path "))) {
16910 	    // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16911 	    opt->textValue = p+7;
16912 	    opt->type = PathName; // PathName;
16913 	} else if(p = strstr(opt->name, " -check ")) {
16914 	    if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16915 	    opt->value = (def != 0);
16916 	    opt->type = CheckBox;
16917 	} else if(p = strstr(opt->name,	" -combo ")) {
16918 	    opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16919 	    cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16920 	    if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16921 	    opt->value = n = 0;
16922 	    while(q = StrStr(q, " /// ")) {
16923 		n++; *q = 0;    // count choices, and null-terminate each of them
16924 		q += 5;
16925 		if(*q == '*') { // remember default, which is marked with * prefix
16926 		    q++;
16927 		    opt->value = n;
16928 		}
16929 		cps->comboList[cps->comboCnt++] = q;
16930 	    }
16931 	    cps->comboList[cps->comboCnt++] = NULL;
16932 	    opt->max = n + 1;
16933 	    opt->type = ComboBox;
16934 	} else if(p = strstr(opt->name, " -button")) {
16935 	    opt->type = Button;
16936 	} else if(p = strstr(opt->name, " -save")) {
16937 	    opt->type = SaveButton;
16938 	} else return FALSE;
16939 	*p = 0; // terminate option name
16940 	// now look if the command-line options define a setting for this engine option.
16941 	if(cps->optionSettings && cps->optionSettings[0])
16942 	    p = strstr(cps->optionSettings, opt->name); else p = NULL;
16943 	if(p && (p == cps->optionSettings || p[-1] == ',')) {
16944 	  snprintf(buf, MSG_SIZ, "option %s", p);
16945 		if(p = strstr(buf, ",")) *p = 0;
16946 		if(q = strchr(buf, '=')) switch(opt->type) {
16947 		    case ComboBox:
16948 			for(n=0; n<opt->max; n++)
16949 			    if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16950 			break;
16951 		    case TextBox:
16952 			safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16953 			break;
16954 		    case Spin:
16955 		    case CheckBox:
16956 			opt->value = atoi(q+1);
16957 		    default:
16958 			break;
16959 		}
16960 		strcat(buf, "\n");
16961 		SendToProgram(buf, cps);
16962 	}
16963 	return TRUE;
16964 }
16965 
16966 void
16967 FeatureDone (ChessProgramState *cps, int val)
16968 {
16969   DelayedEventCallback cb = GetDelayedEvent();
16970   if ((cb == InitBackEnd3 && cps == &first) ||
16971       (cb == SettingsMenuIfReady && cps == &second) ||
16972       (cb == LoadEngine) ||
16973       (cb == TwoMachinesEventIfReady)) {
16974     CancelDelayedEvent();
16975     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16976   }
16977   cps->initDone = val;
16978   if(val) cps->reload = FALSE;
16979 }
16980 
16981 /* Parse feature command from engine */
16982 void
16983 ParseFeatures (char *args, ChessProgramState *cps)
16984 {
16985   char *p = args;
16986   char *q = NULL;
16987   int val;
16988   char buf[MSG_SIZ];
16989 
16990   for (;;) {
16991     while (*p == ' ') p++;
16992     if (*p == NULLCHAR) return;
16993 
16994     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16995     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16996     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16997     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16998     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16999     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17000     if (BoolFeature(&p, "reuse", &val, cps)) {
17001       /* Engine can disable reuse, but can't enable it if user said no */
17002       if (!val) cps->reuse = FALSE;
17003       continue;
17004     }
17005     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17006     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17007       if (gameMode == TwoMachinesPlay) {
17008 	DisplayTwoMachinesTitle();
17009       } else {
17010 	DisplayTitle("");
17011       }
17012       continue;
17013     }
17014     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17015     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17016     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17017     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17018     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17019     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17020     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17021     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17022     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17023     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17024     if (IntFeature(&p, "done", &val, cps)) {
17025       FeatureDone(cps, val);
17026       continue;
17027     }
17028     /* Added by Tord: */
17029     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17030     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17031     /* End of additions by Tord */
17032 
17033     /* [HGM] added features: */
17034     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17035     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17036     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17037     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17038     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17039     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17040     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17041     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17042 	if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17043 	FREE(cps->option[cps->nrOptions].name);
17044 	cps->option[cps->nrOptions].name = q; q = NULL;
17045 	if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17046 	  snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17047 	    SendToProgram(buf, cps);
17048 	    continue;
17049 	}
17050 	if(cps->nrOptions >= MAX_OPTIONS) {
17051 	    cps->nrOptions--;
17052 	    snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17053 	    DisplayError(buf, 0);
17054 	}
17055 	continue;
17056     }
17057     /* End of additions by HGM */
17058 
17059     /* unknown feature: complain and skip */
17060     q = p;
17061     while (*q && *q != '=') q++;
17062     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17063     SendToProgram(buf, cps);
17064     p = q;
17065     if (*p == '=') {
17066       p++;
17067       if (*p == '\"') {
17068 	p++;
17069 	while (*p && *p != '\"') p++;
17070 	if (*p == '\"') p++;
17071       } else {
17072 	while (*p && *p != ' ') p++;
17073       }
17074     }
17075   }
17076 
17077 }
17078 
17079 void
17080 PeriodicUpdatesEvent (int newState)
17081 {
17082     if (newState == appData.periodicUpdates)
17083       return;
17084 
17085     appData.periodicUpdates=newState;
17086 
17087     /* Display type changes, so update it now */
17088 //    DisplayAnalysis();
17089 
17090     /* Get the ball rolling again... */
17091     if (newState) {
17092 	AnalysisPeriodicEvent(1);
17093 	StartAnalysisClock();
17094     }
17095 }
17096 
17097 void
17098 PonderNextMoveEvent (int newState)
17099 {
17100     if (newState == appData.ponderNextMove) return;
17101     if (gameMode == EditPosition) EditPositionDone(TRUE);
17102     if (newState) {
17103 	SendToProgram("hard\n", &first);
17104 	if (gameMode == TwoMachinesPlay) {
17105 	    SendToProgram("hard\n", &second);
17106 	}
17107     } else {
17108 	SendToProgram("easy\n", &first);
17109 	thinkOutput[0] = NULLCHAR;
17110 	if (gameMode == TwoMachinesPlay) {
17111 	    SendToProgram("easy\n", &second);
17112 	}
17113     }
17114     appData.ponderNextMove = newState;
17115 }
17116 
17117 void
17118 NewSettingEvent (int option, int *feature, char *command, int value)
17119 {
17120     char buf[MSG_SIZ];
17121 
17122     if (gameMode == EditPosition) EditPositionDone(TRUE);
17123     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17124     if(feature == NULL || *feature) SendToProgram(buf, &first);
17125     if (gameMode == TwoMachinesPlay) {
17126 	if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17127     }
17128 }
17129 
17130 void
17131 ShowThinkingEvent ()
17132 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17133 {
17134     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17135     int newState = appData.showThinking
17136 	// [HGM] thinking: other features now need thinking output as well
17137 	|| !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17138 
17139     if (oldState == newState) return;
17140     oldState = newState;
17141     if (gameMode == EditPosition) EditPositionDone(TRUE);
17142     if (oldState) {
17143 	SendToProgram("post\n", &first);
17144 	if (gameMode == TwoMachinesPlay) {
17145 	    SendToProgram("post\n", &second);
17146 	}
17147     } else {
17148 	SendToProgram("nopost\n", &first);
17149 	thinkOutput[0] = NULLCHAR;
17150 	if (gameMode == TwoMachinesPlay) {
17151 	    SendToProgram("nopost\n", &second);
17152 	}
17153     }
17154 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17155 }
17156 
17157 void
17158 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17159 {
17160   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17161   if (pr == NoProc) return;
17162   AskQuestion(title, question, replyPrefix, pr);
17163 }
17164 
17165 void
17166 TypeInEvent (char firstChar)
17167 {
17168     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17169         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17170 	gameMode == AnalyzeMode || gameMode == EditGame ||
17171 	gameMode == EditPosition || gameMode == IcsExamining ||
17172 	gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17173 	isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17174 		( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17175 		  gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17176 	gameMode == Training) PopUpMoveDialog(firstChar);
17177 }
17178 
17179 void
17180 TypeInDoneEvent (char *move)
17181 {
17182 	Board board;
17183 	int n, fromX, fromY, toX, toY;
17184 	char promoChar;
17185 	ChessMove moveType;
17186 
17187 	// [HGM] FENedit
17188 	if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17189 		EditPositionPasteFEN(move);
17190 		return;
17191 	}
17192 	// [HGM] movenum: allow move number to be typed in any mode
17193 	if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17194 	  ToNrEvent(2*n-1);
17195 	  return;
17196 	}
17197 	// undocumented kludge: allow command-line option to be typed in!
17198 	// (potentially fatal, and does not implement the effect of the option.)
17199 	// should only be used for options that are values on which future decisions will be made,
17200 	// and definitely not on options that would be used during initialization.
17201 	if(strstr(move, "!!! -") == move) {
17202 	    ParseArgsFromString(move+4);
17203 	    return;
17204         }
17205 
17206       if (gameMode != EditGame && currentMove != forwardMostMove &&
17207 	gameMode != Training) {
17208 	DisplayMoveError(_("Displayed move is not current"));
17209       } else {
17210 	int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17211 	  &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17212 	if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17213 	if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17214 	  &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17215 	  UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17216 	} else {
17217 	  DisplayMoveError(_("Could not parse move"));
17218 	}
17219       }
17220 }
17221 
17222 void
17223 DisplayMove (int moveNumber)
17224 {
17225     char message[MSG_SIZ];
17226     char res[MSG_SIZ];
17227     char cpThinkOutput[MSG_SIZ];
17228 
17229     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17230 
17231     if (moveNumber == forwardMostMove - 1 ||
17232 	gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17233 
17234 	safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17235 
17236         if (strchr(cpThinkOutput, '\n')) {
17237 	    *strchr(cpThinkOutput, '\n') = NULLCHAR;
17238         }
17239     } else {
17240 	*cpThinkOutput = NULLCHAR;
17241     }
17242 
17243     /* [AS] Hide thinking from human user */
17244     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17245         *cpThinkOutput = NULLCHAR;
17246         if( thinkOutput[0] != NULLCHAR ) {
17247             int i;
17248 
17249             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17250                 cpThinkOutput[i] = '.';
17251             }
17252             cpThinkOutput[i] = NULLCHAR;
17253             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17254         }
17255     }
17256 
17257     if (moveNumber == forwardMostMove - 1 &&
17258 	gameInfo.resultDetails != NULL) {
17259 	if (gameInfo.resultDetails[0] == NULLCHAR) {
17260 	  snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17261 	} else {
17262 	  snprintf(res, MSG_SIZ, " {%s} %s",
17263 		    T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17264 	}
17265     } else {
17266 	res[0] = NULLCHAR;
17267     }
17268 
17269     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17270 	DisplayMessage(res, cpThinkOutput);
17271     } else {
17272       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17273 		WhiteOnMove(moveNumber) ? " " : ".. ",
17274 		parseList[moveNumber], res);
17275 	DisplayMessage(message, cpThinkOutput);
17276     }
17277 }
17278 
17279 void
17280 DisplayComment (int moveNumber, char *text)
17281 {
17282     char title[MSG_SIZ];
17283 
17284     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17285       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17286     } else {
17287       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17288 	      WhiteOnMove(moveNumber) ? " " : ".. ",
17289 	      parseList[moveNumber]);
17290     }
17291     if (text != NULL && (appData.autoDisplayComment || commentUp))
17292         CommentPopUp(title, text);
17293 }
17294 
17295 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17296  * might be busy thinking or pondering.  It can be omitted if your
17297  * gnuchess is configured to stop thinking immediately on any user
17298  * input.  However, that gnuchess feature depends on the FIONREAD
17299  * ioctl, which does not work properly on some flavors of Unix.
17300  */
17301 void
17302 Attention (ChessProgramState *cps)
17303 {
17304 #if ATTENTION
17305     if (!cps->useSigint) return;
17306     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17307     switch (gameMode) {
17308       case MachinePlaysWhite:
17309       case MachinePlaysBlack:
17310       case TwoMachinesPlay:
17311       case IcsPlayingWhite:
17312       case IcsPlayingBlack:
17313       case AnalyzeMode:
17314       case AnalyzeFile:
17315 	/* Skip if we know it isn't thinking */
17316 	if (!cps->maybeThinking) return;
17317 	if (appData.debugMode)
17318 	  fprintf(debugFP, "Interrupting %s\n", cps->which);
17319 	InterruptChildProcess(cps->pr);
17320 	cps->maybeThinking = FALSE;
17321 	break;
17322       default:
17323 	break;
17324     }
17325 #endif /*ATTENTION*/
17326 }
17327 
17328 int
17329 CheckFlags ()
17330 {
17331     if (whiteTimeRemaining <= 0) {
17332 	if (!whiteFlag) {
17333 	    whiteFlag = TRUE;
17334 	    if (appData.icsActive) {
17335 		if (appData.autoCallFlag &&
17336 		    gameMode == IcsPlayingBlack && !blackFlag) {
17337 		  SendToICS(ics_prefix);
17338 		  SendToICS("flag\n");
17339 		}
17340 	    } else {
17341 		if (blackFlag) {
17342                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17343 		} else {
17344                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17345  		    if (appData.autoCallFlag) {
17346 			GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17347 			return TRUE;
17348 		    }
17349 		}
17350 	    }
17351 	}
17352     }
17353     if (blackTimeRemaining <= 0) {
17354 	if (!blackFlag) {
17355 	    blackFlag = TRUE;
17356 	    if (appData.icsActive) {
17357 		if (appData.autoCallFlag &&
17358 		    gameMode == IcsPlayingWhite && !whiteFlag) {
17359 		  SendToICS(ics_prefix);
17360 		  SendToICS("flag\n");
17361 		}
17362 	    } else {
17363 		if (whiteFlag) {
17364                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17365 		} else {
17366                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17367 		    if (appData.autoCallFlag) {
17368 			GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17369 			return TRUE;
17370 		    }
17371 		}
17372 	    }
17373 	}
17374     }
17375     return FALSE;
17376 }
17377 
17378 void
17379 CheckTimeControl ()
17380 {
17381     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17382 	gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17383 
17384     /*
17385      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17386      */
17387     if ( !WhiteOnMove(forwardMostMove) ) {
17388 	/* White made time control */
17389         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17390         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17391         /* [HGM] time odds: correct new time quota for time odds! */
17392                                             / WhitePlayer()->timeOdds;
17393         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17394     } else {
17395         lastBlack -= blackTimeRemaining;
17396 	/* Black made time control */
17397         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17398                                             / WhitePlayer()->other->timeOdds;
17399         lastWhite = whiteTimeRemaining;
17400     }
17401 }
17402 
17403 void
17404 DisplayBothClocks ()
17405 {
17406     int wom = gameMode == EditPosition ?
17407       !blackPlaysFirst : WhiteOnMove(currentMove);
17408     DisplayWhiteClock(whiteTimeRemaining, wom);
17409     DisplayBlackClock(blackTimeRemaining, !wom);
17410 }
17411 
17412 
17413 /* Timekeeping seems to be a portability nightmare.  I think everyone
17414    has ftime(), but I'm really not sure, so I'm including some ifdefs
17415    to use other calls if you don't.  Clocks will be less accurate if
17416    you have neither ftime nor gettimeofday.
17417 */
17418 
17419 /* VS 2008 requires the #include outside of the function */
17420 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17421 #include <sys/timeb.h>
17422 #endif
17423 
17424 /* Get the current time as a TimeMark */
17425 void
17426 GetTimeMark (TimeMark *tm)
17427 {
17428 #if HAVE_GETTIMEOFDAY
17429 
17430     struct timeval timeVal;
17431     struct timezone timeZone;
17432 
17433     gettimeofday(&timeVal, &timeZone);
17434     tm->sec = (long) timeVal.tv_sec;
17435     tm->ms = (int) (timeVal.tv_usec / 1000L);
17436 
17437 #else /*!HAVE_GETTIMEOFDAY*/
17438 #if HAVE_FTIME
17439 
17440 // include <sys/timeb.h> / moved to just above start of function
17441     struct timeb timeB;
17442 
17443     ftime(&timeB);
17444     tm->sec = (long) timeB.time;
17445     tm->ms = (int) timeB.millitm;
17446 
17447 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17448     tm->sec = (long) time(NULL);
17449     tm->ms = 0;
17450 #endif
17451 #endif
17452 }
17453 
17454 /* Return the difference in milliseconds between two
17455    time marks.  We assume the difference will fit in a long!
17456 */
17457 long
17458 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17459 {
17460     return 1000L*(tm2->sec - tm1->sec) +
17461            (long) (tm2->ms - tm1->ms);
17462 }
17463 
17464 
17465 /*
17466  * Code to manage the game clocks.
17467  *
17468  * In tournament play, black starts the clock and then white makes a move.
17469  * We give the human user a slight advantage if he is playing white---the
17470  * clocks don't run until he makes his first move, so it takes zero time.
17471  * Also, we don't account for network lag, so we could get out of sync
17472  * with GNU Chess's clock -- but then, referees are always right.
17473  */
17474 
17475 static TimeMark tickStartTM;
17476 static long intendedTickLength;
17477 
17478 long
17479 NextTickLength (long timeRemaining)
17480 {
17481     long nominalTickLength, nextTickLength;
17482 
17483     if (timeRemaining > 0L && timeRemaining <= 10000L)
17484       nominalTickLength = 100L;
17485     else
17486       nominalTickLength = 1000L;
17487     nextTickLength = timeRemaining % nominalTickLength;
17488     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17489 
17490     return nextTickLength;
17491 }
17492 
17493 /* Adjust clock one minute up or down */
17494 void
17495 AdjustClock (Boolean which, int dir)
17496 {
17497     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17498     if(which) blackTimeRemaining += 60000*dir;
17499     else      whiteTimeRemaining += 60000*dir;
17500     DisplayBothClocks();
17501     adjustedClock = TRUE;
17502 }
17503 
17504 /* Stop clocks and reset to a fresh time control */
17505 void
17506 ResetClocks ()
17507 {
17508     (void) StopClockTimer();
17509     if (appData.icsActive) {
17510 	whiteTimeRemaining = blackTimeRemaining = 0;
17511     } else if (searchTime) {
17512 	whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17513 	blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17514     } else { /* [HGM] correct new time quote for time odds */
17515         whiteTC = blackTC = fullTimeControlString;
17516         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17517         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17518     }
17519     if (whiteFlag || blackFlag) {
17520 	DisplayTitle("");
17521 	whiteFlag = blackFlag = FALSE;
17522     }
17523     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17524     DisplayBothClocks();
17525     adjustedClock = FALSE;
17526 }
17527 
17528 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17529 
17530 /* Decrement running clock by amount of time that has passed */
17531 void
17532 DecrementClocks ()
17533 {
17534     long timeRemaining;
17535     long lastTickLength, fudge;
17536     TimeMark now;
17537 
17538     if (!appData.clockMode) return;
17539     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17540 
17541     GetTimeMark(&now);
17542 
17543     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17544 
17545     /* Fudge if we woke up a little too soon */
17546     fudge = intendedTickLength - lastTickLength;
17547     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17548 
17549     if (WhiteOnMove(forwardMostMove)) {
17550 	if(whiteNPS >= 0) lastTickLength = 0;
17551 	timeRemaining = whiteTimeRemaining -= lastTickLength;
17552         if(timeRemaining < 0 && !appData.icsActive) {
17553             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17554             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17555                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17556                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17557             }
17558         }
17559 	DisplayWhiteClock(whiteTimeRemaining - fudge,
17560 			  WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17561     } else {
17562 	if(blackNPS >= 0) lastTickLength = 0;
17563 	timeRemaining = blackTimeRemaining -= lastTickLength;
17564         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17565             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17566             if(suddenDeath) {
17567                 blackStartMove = forwardMostMove;
17568                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17569             }
17570         }
17571 	DisplayBlackClock(blackTimeRemaining - fudge,
17572 			  !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17573     }
17574     if (CheckFlags()) return;
17575 
17576     if(twoBoards) { // count down secondary board's clocks as well
17577 	activePartnerTime -= lastTickLength;
17578 	partnerUp = 1;
17579 	if(activePartner == 'W')
17580 	    DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17581 	else
17582 	    DisplayBlackClock(activePartnerTime, TRUE);
17583 	partnerUp = 0;
17584     }
17585 
17586     tickStartTM = now;
17587     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17588     StartClockTimer(intendedTickLength);
17589 
17590     /* if the time remaining has fallen below the alarm threshold, sound the
17591      * alarm. if the alarm has sounded and (due to a takeback or time control
17592      * with increment) the time remaining has increased to a level above the
17593      * threshold, reset the alarm so it can sound again.
17594      */
17595 
17596     if (appData.icsActive && appData.icsAlarm) {
17597 
17598 	/* make sure we are dealing with the user's clock */
17599 	if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17600 	       ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17601 	   )) return;
17602 
17603 	if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17604 	    alarmSounded = FALSE;
17605 	} else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17606 	    PlayAlarmSound();
17607 	    alarmSounded = TRUE;
17608 	}
17609     }
17610 }
17611 
17612 
17613 /* A player has just moved, so stop the previously running
17614    clock and (if in clock mode) start the other one.
17615    We redisplay both clocks in case we're in ICS mode, because
17616    ICS gives us an update to both clocks after every move.
17617    Note that this routine is called *after* forwardMostMove
17618    is updated, so the last fractional tick must be subtracted
17619    from the color that is *not* on move now.
17620 */
17621 void
17622 SwitchClocks (int newMoveNr)
17623 {
17624     long lastTickLength;
17625     TimeMark now;
17626     int flagged = FALSE;
17627 
17628     GetTimeMark(&now);
17629 
17630     if (StopClockTimer() && appData.clockMode) {
17631 	lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17632 	if (!WhiteOnMove(forwardMostMove)) {
17633 	    if(blackNPS >= 0) lastTickLength = 0;
17634 	    blackTimeRemaining -= lastTickLength;
17635            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17636 //         if(pvInfoList[forwardMostMove].time == -1)
17637                  pvInfoList[forwardMostMove].time =               // use GUI time
17638                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17639 	} else {
17640 	   if(whiteNPS >= 0) lastTickLength = 0;
17641 	   whiteTimeRemaining -= lastTickLength;
17642            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17643 //         if(pvInfoList[forwardMostMove].time == -1)
17644                  pvInfoList[forwardMostMove].time =
17645                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17646 	}
17647 	flagged = CheckFlags();
17648     }
17649     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17650     CheckTimeControl();
17651 
17652     if (flagged || !appData.clockMode) return;
17653 
17654     switch (gameMode) {
17655       case MachinePlaysBlack:
17656       case MachinePlaysWhite:
17657       case BeginningOfGame:
17658 	if (pausing) return;
17659 	break;
17660 
17661       case EditGame:
17662       case PlayFromGameFile:
17663       case IcsExamining:
17664 	return;
17665 
17666       default:
17667 	break;
17668     }
17669 
17670     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17671 	if(WhiteOnMove(forwardMostMove))
17672 	     whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17673 	else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17674     }
17675 
17676     tickStartTM = now;
17677     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17678       whiteTimeRemaining : blackTimeRemaining);
17679     StartClockTimer(intendedTickLength);
17680 }
17681 
17682 
17683 /* Stop both clocks */
17684 void
17685 StopClocks ()
17686 {
17687     long lastTickLength;
17688     TimeMark now;
17689 
17690     if (!StopClockTimer()) return;
17691     if (!appData.clockMode) return;
17692 
17693     GetTimeMark(&now);
17694 
17695     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17696     if (WhiteOnMove(forwardMostMove)) {
17697 	if(whiteNPS >= 0) lastTickLength = 0;
17698 	whiteTimeRemaining -= lastTickLength;
17699 	DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17700     } else {
17701 	if(blackNPS >= 0) lastTickLength = 0;
17702 	blackTimeRemaining -= lastTickLength;
17703 	DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17704     }
17705     CheckFlags();
17706 }
17707 
17708 /* Start clock of player on move.  Time may have been reset, so
17709    if clock is already running, stop and restart it. */
17710 void
17711 StartClocks ()
17712 {
17713     (void) StopClockTimer(); /* in case it was running already */
17714     DisplayBothClocks();
17715     if (CheckFlags()) return;
17716 
17717     if (!appData.clockMode) return;
17718     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17719 
17720     GetTimeMark(&tickStartTM);
17721     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17722       whiteTimeRemaining : blackTimeRemaining);
17723 
17724    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17725     whiteNPS = blackNPS = -1;
17726     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17727        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17728 	whiteNPS = first.nps;
17729     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17730        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17731 	blackNPS = first.nps;
17732     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17733 	whiteNPS = second.nps;
17734     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17735 	blackNPS = second.nps;
17736     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17737 
17738     StartClockTimer(intendedTickLength);
17739 }
17740 
17741 char *
17742 TimeString (long ms)
17743 {
17744     long second, minute, hour, day;
17745     char *sign = "";
17746     static char buf[32];
17747 
17748     if (ms > 0 && ms <= 9900) {
17749       /* convert milliseconds to tenths, rounding up */
17750       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17751 
17752       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17753       return buf;
17754     }
17755 
17756     /* convert milliseconds to seconds, rounding up */
17757     /* use floating point to avoid strangeness of integer division
17758        with negative dividends on many machines */
17759     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17760 
17761     if (second < 0) {
17762 	sign = "-";
17763 	second = -second;
17764     }
17765 
17766     day = second / (60 * 60 * 24);
17767     second = second % (60 * 60 * 24);
17768     hour = second / (60 * 60);
17769     second = second % (60 * 60);
17770     minute = second / 60;
17771     second = second % 60;
17772 
17773     if (day > 0)
17774       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17775 	      sign, day, hour, minute, second);
17776     else if (hour > 0)
17777       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17778     else
17779       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17780 
17781     return buf;
17782 }
17783 
17784 
17785 /*
17786  * This is necessary because some C libraries aren't ANSI C compliant yet.
17787  */
17788 char *
17789 StrStr (char *string, char *match)
17790 {
17791     int i, length;
17792 
17793     length = strlen(match);
17794 
17795     for (i = strlen(string) - length; i >= 0; i--, string++)
17796       if (!strncmp(match, string, length))
17797 	return string;
17798 
17799     return NULL;
17800 }
17801 
17802 char *
17803 StrCaseStr (char *string, char *match)
17804 {
17805     int i, j, length;
17806 
17807     length = strlen(match);
17808 
17809     for (i = strlen(string) - length; i >= 0; i--, string++) {
17810 	for (j = 0; j < length; j++) {
17811 	    if (ToLower(match[j]) != ToLower(string[j]))
17812 	      break;
17813 	}
17814 	if (j == length) return string;
17815     }
17816 
17817     return NULL;
17818 }
17819 
17820 #ifndef _amigados
17821 int
17822 StrCaseCmp (char *s1, char *s2)
17823 {
17824     char c1, c2;
17825 
17826     for (;;) {
17827 	c1 = ToLower(*s1++);
17828 	c2 = ToLower(*s2++);
17829 	if (c1 > c2) return 1;
17830 	if (c1 < c2) return -1;
17831 	if (c1 == NULLCHAR) return 0;
17832     }
17833 }
17834 
17835 
17836 int
17837 ToLower (int c)
17838 {
17839     return isupper(c) ? tolower(c) : c;
17840 }
17841 
17842 
17843 int
17844 ToUpper (int c)
17845 {
17846     return islower(c) ? toupper(c) : c;
17847 }
17848 #endif /* !_amigados	*/
17849 
17850 char *
17851 StrSave (char *s)
17852 {
17853   char *ret;
17854 
17855   if ((ret = (char *) malloc(strlen(s) + 1)))
17856     {
17857       safeStrCpy(ret, s, strlen(s)+1);
17858     }
17859   return ret;
17860 }
17861 
17862 char *
17863 StrSavePtr (char *s, char **savePtr)
17864 {
17865     if (*savePtr) {
17866 	free(*savePtr);
17867     }
17868     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17869       safeStrCpy(*savePtr, s, strlen(s)+1);
17870     }
17871     return(*savePtr);
17872 }
17873 
17874 char *
17875 PGNDate ()
17876 {
17877     time_t clock;
17878     struct tm *tm;
17879     char buf[MSG_SIZ];
17880 
17881     clock = time((time_t *)NULL);
17882     tm = localtime(&clock);
17883     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17884 	    tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17885     return StrSave(buf);
17886 }
17887 
17888 
17889 char *
17890 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17891 {
17892     int i, j, fromX, fromY, toX, toY;
17893     int whiteToPlay;
17894     char buf[MSG_SIZ];
17895     char *p, *q;
17896     int emptycount;
17897     ChessSquare piece;
17898 
17899     whiteToPlay = (gameMode == EditPosition) ?
17900       !blackPlaysFirst : (move % 2 == 0);
17901     p = buf;
17902 
17903     /* Piece placement data */
17904     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17905 	if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17906 	emptycount = 0;
17907         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17908 	    if (boards[move][i][j] == EmptySquare) {
17909 		emptycount++;
17910             } else { ChessSquare piece = boards[move][i][j];
17911 		if (emptycount > 0) {
17912                     if(emptycount<10) /* [HGM] can be >= 10 */
17913                         *p++ = '0' + emptycount;
17914                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17915 		    emptycount = 0;
17916 		}
17917                 if(PieceToChar(piece) == '+') {
17918                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17919                     *p++ = '+';
17920                     piece = (ChessSquare)(CHUDEMOTED piece);
17921                 }
17922                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17923                 if(*p = PieceSuffix(piece)) p++;
17924                 if(p[-1] == '~') {
17925                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17926                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17927                     *p++ = '~';
17928                 }
17929 	    }
17930 	}
17931 	if (emptycount > 0) {
17932             if(emptycount<10) /* [HGM] can be >= 10 */
17933                 *p++ = '0' + emptycount;
17934             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17935 	    emptycount = 0;
17936 	}
17937 	*p++ = '/';
17938     }
17939     *(p - 1) = ' ';
17940 
17941     /* [HGM] print Crazyhouse or Shogi holdings */
17942     if( gameInfo.holdingsWidth ) {
17943         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17944         q = p;
17945         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17946             piece = boards[move][i][BOARD_WIDTH-1];
17947             if( piece != EmptySquare )
17948               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17949                   *p++ = PieceToChar(piece);
17950         }
17951         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17952             piece = boards[move][BOARD_HEIGHT-i-1][0];
17953             if( piece != EmptySquare )
17954               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17955                   *p++ = PieceToChar(piece);
17956         }
17957 
17958         if( q == p ) *p++ = '-';
17959         *p++ = ']';
17960         *p++ = ' ';
17961     }
17962 
17963     /* Active color */
17964     *p++ = whiteToPlay ? 'w' : 'b';
17965     *p++ = ' ';
17966 
17967   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17968     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17969   } else {
17970   if(nrCastlingRights) {
17971      int handW=0, handB=0;
17972      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17973 	for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17974 	for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17975      }
17976      q = p;
17977      if(appData.fischerCastling) {
17978 	if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17979            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17980                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17981 	} else {
17982        /* [HGM] write directly from rights */
17983            if(boards[move][CASTLING][2] != NoRights &&
17984               boards[move][CASTLING][0] != NoRights   )
17985                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17986            if(boards[move][CASTLING][2] != NoRights &&
17987               boards[move][CASTLING][1] != NoRights   )
17988                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17989 	}
17990 	if(handB) {
17991            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17992                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17993 	} else {
17994            if(boards[move][CASTLING][5] != NoRights &&
17995               boards[move][CASTLING][3] != NoRights   )
17996                 *p++ = boards[move][CASTLING][3] + AAA;
17997            if(boards[move][CASTLING][5] != NoRights &&
17998               boards[move][CASTLING][4] != NoRights   )
17999                 *p++ = boards[move][CASTLING][4] + AAA;
18000 	}
18001      } else {
18002 
18003         /* [HGM] write true castling rights */
18004         if( nrCastlingRights == 6 ) {
18005             int q, k=0;
18006             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
18007                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18008             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
18009                  boards[move][CASTLING][2] != NoRights  );
18010             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18011                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18012                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18013                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18014             }
18015 	    if(q) *p++ = 'Q';
18016             k = 0;
18017             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
18018                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18019             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
18020                  boards[move][CASTLING][5] != NoRights  );
18021             if(handB) {
18022                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18023                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18024                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18025             }
18026             if(q) *p++ = 'q';
18027         }
18028      }
18029      if (q == p) *p++ = '-'; /* No castling rights */
18030      *p++ = ' ';
18031   }
18032 
18033   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18034      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18035      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18036     /* En passant target square */
18037     if (move > backwardMostMove) {
18038         fromX = moveList[move - 1][0] - AAA;
18039         fromY = moveList[move - 1][1] - ONE;
18040         toX = moveList[move - 1][2] - AAA;
18041         toY = moveList[move - 1][3] - ONE;
18042 	if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18043 	    toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18044 	    boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18045 	    fromX == toX) {
18046 	    /* 2-square pawn move just happened */
18047             *p++ = toX + AAA;
18048 	    *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18049 	} else {
18050 	    *p++ = '-';
18051 	}
18052     } else if(move == backwardMostMove) {
18053 	// [HGM] perhaps we should always do it like this, and forget the above?
18054 	if((signed char)boards[move][EP_STATUS] >= 0) {
18055 	    *p++ = boards[move][EP_STATUS] + AAA;
18056 	    *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18057 	} else {
18058 	    *p++ = '-';
18059 	}
18060     } else {
18061 	*p++ = '-';
18062     }
18063     *p++ = ' ';
18064   }
18065   }
18066 
18067     if(moveCounts)
18068     {   int i = 0, j=move;
18069 
18070         /* [HGM] find reversible plies */
18071         if (appData.debugMode) { int k;
18072             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18073             for(k=backwardMostMove; k<=forwardMostMove; k++)
18074                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18075 
18076         }
18077 
18078         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18079         if( j == backwardMostMove ) i += initialRulePlies;
18080         sprintf(p, "%d ", i);
18081         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18082 
18083         /* Fullmove number */
18084         sprintf(p, "%d", (move / 2) + 1);
18085     } else *--p = NULLCHAR;
18086 
18087     return StrSave(buf);
18088 }
18089 
18090 Boolean
18091 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18092 {
18093     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18094     char *p, c;
18095     int emptycount, virgin[BOARD_FILES];
18096     ChessSquare piece;
18097 
18098     p = fen;
18099 
18100     /* Piece placement data */
18101     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18102 	j = 0;
18103 	for (;;) {
18104             if (*p == '/' || *p == ' ' || *p == '[' ) {
18105 		if(j > w) w = j;
18106                 emptycount = gameInfo.boardWidth - j;
18107                 while (emptycount--)
18108                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18109                 if (*p == '/') p++;
18110 		else if(autoSize) { // we stumbled unexpectedly into end of board
18111                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18112 		        for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18113                     }
18114 		    appData.NrRanks = gameInfo.boardHeight - i; i=0;
18115                 }
18116 		break;
18117 #if(BOARD_FILES >= 10)*0
18118             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18119                 p++; emptycount=10;
18120                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18121                 while (emptycount--)
18122                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18123 #endif
18124             } else if (*p == '*') {
18125 		board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18126             } else if (isdigit(*p)) {
18127 		emptycount = *p++ - '0';
18128                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18129                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18130                 while (emptycount--)
18131                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18132             } else if (*p == '<') {
18133                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18134                 else if (i != 0 || !shuffle) return FALSE;
18135                 p++;
18136             } else if (shuffle && *p == '>') {
18137                 p++; // for now ignore closing shuffle range, and assume rank-end
18138             } else if (*p == '?') {
18139                 if (j >= gameInfo.boardWidth) return FALSE;
18140                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18141 		board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18142             } else if (*p == '+' || isalpha(*p)) {
18143 		char *q, *s = SUFFIXES;
18144                 if (j >= gameInfo.boardWidth) return FALSE;
18145                 if(*p=='+') {
18146                     char c = *++p;
18147                     if(q = strchr(s, p[1])) p++;
18148                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18149                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18150                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18151                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18152                 } else {
18153                     char c = *p++;
18154 		    if(q = strchr(s, *p)) p++;
18155 		    piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18156 		}
18157 
18158                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18159                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18160                     piece = (ChessSquare) (PROMOTED piece);
18161                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18162                     p++;
18163                 }
18164                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18165                 if(piece == WhiteKing) wKingRank = i;
18166                 if(piece == BlackKing) bKingRank = i;
18167 	    } else {
18168 		return FALSE;
18169 	    }
18170 	}
18171     }
18172     while (*p == '/' || *p == ' ') p++;
18173 
18174     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18175 
18176     /* [HGM] by default clear Crazyhouse holdings, if present */
18177     if(gameInfo.holdingsWidth) {
18178        for(i=0; i<BOARD_HEIGHT; i++) {
18179            board[i][0]             = EmptySquare; /* black holdings */
18180            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18181            board[i][1]             = (ChessSquare) 0; /* black counts */
18182            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18183        }
18184     }
18185 
18186     /* [HGM] look for Crazyhouse holdings here */
18187     while(*p==' ') p++;
18188     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18189         int swap=0, wcnt=0, bcnt=0;
18190         if(*p == '[') p++;
18191         if(*p == '<') swap++, p++;
18192         if(*p == '-' ) p++; /* empty holdings */ else {
18193             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18194             /* if we would allow FEN reading to set board size, we would   */
18195             /* have to add holdings and shift the board read so far here   */
18196             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18197                 p++;
18198                 if((int) piece >= (int) BlackPawn ) {
18199                     i = (int)piece - (int)BlackPawn;
18200 		    i = PieceToNumber((ChessSquare)i);
18201                     if( i >= gameInfo.holdingsSize ) return FALSE;
18202                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18203                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18204                     bcnt++;
18205                 } else {
18206                     i = (int)piece - (int)WhitePawn;
18207 		    i = PieceToNumber((ChessSquare)i);
18208                     if( i >= gameInfo.holdingsSize ) return FALSE;
18209                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18210                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18211                     wcnt++;
18212                 }
18213             }
18214             if(subst) { // substitute back-rank question marks by holdings pieces
18215                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18216                     int k, m, n = bcnt + 1;
18217                     if(board[0][j] == ClearBoard) {
18218                         if(!wcnt) return FALSE;
18219                         n = rand() % wcnt;
18220                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18221                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18222                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18223                             break;
18224                         }
18225                     }
18226                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18227                         if(!bcnt) return FALSE;
18228                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18229                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18230                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18231                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18232                             break;
18233                         }
18234                     }
18235                 }
18236                 subst = 0;
18237             }
18238         }
18239         if(*p == ']') p++;
18240     }
18241 
18242     if(subst) return FALSE; // substitution requested, but no holdings
18243 
18244     while(*p == ' ') p++;
18245 
18246     /* Active color */
18247     c = *p++;
18248     if(appData.colorNickNames) {
18249       if( c == appData.colorNickNames[0] ) c = 'w'; else
18250       if( c == appData.colorNickNames[1] ) c = 'b';
18251     }
18252     switch (c) {
18253       case 'w':
18254         *blackPlaysFirst = FALSE;
18255 	break;
18256       case 'b':
18257 	*blackPlaysFirst = TRUE;
18258 	break;
18259       default:
18260 	return FALSE;
18261     }
18262 
18263     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18264     /* return the extra info in global variiables             */
18265 
18266     while(*p==' ') p++;
18267 
18268     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18269         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18270         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18271     }
18272 
18273     /* set defaults in case FEN is incomplete */
18274     board[EP_STATUS] = EP_UNKNOWN;
18275     for(i=0; i<nrCastlingRights; i++ ) {
18276         board[CASTLING][i] =
18277             appData.fischerCastling ? NoRights : initialRights[i];
18278     }   /* assume possible unless obviously impossible */
18279     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18280     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18281     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18282 				  && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18283     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18284     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18285     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18286 				  && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18287     FENrulePlies = 0;
18288 
18289     if(nrCastlingRights) {
18290       int fischer = 0;
18291       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18292       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18293           /* castling indicator present, so default becomes no castlings */
18294           for(i=0; i<nrCastlingRights; i++ ) {
18295                  board[CASTLING][i] = NoRights;
18296           }
18297       }
18298       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18299              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18300              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18301              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18302         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18303 
18304         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18305             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18306             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18307         }
18308         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18309             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18310         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18311                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18312         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18313                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18314         switch(c) {
18315           case'K':
18316               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18317               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18318               board[CASTLING][2] = whiteKingFile;
18319 	      if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18320 	      if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18321               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18322               break;
18323           case'Q':
18324               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18325               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18326               board[CASTLING][2] = whiteKingFile;
18327 	      if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18328 	      if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18329               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18330               break;
18331           case'k':
18332               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18333               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18334               board[CASTLING][5] = blackKingFile;
18335 	      if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18336 	      if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18337               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18338               break;
18339           case'q':
18340               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18341               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18342               board[CASTLING][5] = blackKingFile;
18343 	      if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18344 	      if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18345               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18346           case '-':
18347               break;
18348           default: /* FRC castlings */
18349               if(c >= 'a') { /* black rights */
18350 		  if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18351                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18352                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18353                   if(i == BOARD_RGHT) break;
18354                   board[CASTLING][5] = i;
18355                   c -= AAA;
18356                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18357                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18358                   if(c > i)
18359                       board[CASTLING][3] = c;
18360                   else
18361                       board[CASTLING][4] = c;
18362               } else { /* white rights */
18363 		  if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18364                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18365                     if(board[0][i] == WhiteKing) break;
18366                   if(i == BOARD_RGHT) break;
18367                   board[CASTLING][2] = i;
18368                   c -= AAA - 'a' + 'A';
18369                   if(board[0][c] >= WhiteKing) break;
18370                   if(c > i)
18371                       board[CASTLING][0] = c;
18372                   else
18373                       board[CASTLING][1] = c;
18374               }
18375         }
18376       }
18377       for(i=0; i<nrCastlingRights; i++)
18378         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18379       if(gameInfo.variant == VariantSChess)
18380         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18381       if(fischer && shuffle) appData.fischerCastling = TRUE;
18382     if (appData.debugMode) {
18383         fprintf(debugFP, "FEN castling rights:");
18384         for(i=0; i<nrCastlingRights; i++)
18385         fprintf(debugFP, " %d", board[CASTLING][i]);
18386         fprintf(debugFP, "\n");
18387     }
18388 
18389       while(*p==' ') p++;
18390     }
18391 
18392     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18393 
18394     /* read e.p. field in games that know e.p. capture */
18395     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18396        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18397        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18398       if(*p=='-') {
18399         p++; board[EP_STATUS] = EP_NONE;
18400       } else {
18401          char c = *p++ - AAA;
18402 
18403          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18404          if(*p >= '0' && *p <='9') p++;
18405          board[EP_STATUS] = c;
18406       }
18407     }
18408 
18409 
18410     if(sscanf(p, "%d", &i) == 1) {
18411         FENrulePlies = i; /* 50-move ply counter */
18412         /* (The move number is still ignored)    */
18413     }
18414 
18415     return TRUE;
18416 }
18417 
18418 void
18419 EditPositionPasteFEN (char *fen)
18420 {
18421   if (fen != NULL) {
18422     Board initial_position;
18423 
18424     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18425       DisplayError(_("Bad FEN position in clipboard"), 0);
18426       return ;
18427     } else {
18428       int savedBlackPlaysFirst = blackPlaysFirst;
18429       EditPositionEvent();
18430       blackPlaysFirst = savedBlackPlaysFirst;
18431       CopyBoard(boards[0], initial_position);
18432       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18433       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18434       DisplayBothClocks();
18435       DrawPosition(FALSE, boards[currentMove]);
18436     }
18437   }
18438 }
18439 
18440 static char cseq[12] = "\\   ";
18441 
18442 Boolean
18443 set_cont_sequence (char *new_seq)
18444 {
18445     int len;
18446     Boolean ret;
18447 
18448     // handle bad attempts to set the sequence
18449 	if (!new_seq)
18450 		return 0; // acceptable error - no debug
18451 
18452     len = strlen(new_seq);
18453     ret = (len > 0) && (len < sizeof(cseq));
18454     if (ret)
18455       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18456     else if (appData.debugMode)
18457       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18458     return ret;
18459 }
18460 
18461 /*
18462     reformat a source message so words don't cross the width boundary.  internal
18463     newlines are not removed.  returns the wrapped size (no null character unless
18464     included in source message).  If dest is NULL, only calculate the size required
18465     for the dest buffer.  lp argument indicats line position upon entry, and it's
18466     passed back upon exit.
18467 */
18468 int
18469 wrap (char *dest, char *src, int count, int width, int *lp)
18470 {
18471     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18472 
18473     cseq_len = strlen(cseq);
18474     old_line = line = *lp;
18475     ansi = len = clen = 0;
18476 
18477     for (i=0; i < count; i++)
18478     {
18479         if (src[i] == '\033')
18480             ansi = 1;
18481 
18482         // if we hit the width, back up
18483         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18484         {
18485             // store i & len in case the word is too long
18486             old_i = i, old_len = len;
18487 
18488             // find the end of the last word
18489             while (i && src[i] != ' ' && src[i] != '\n')
18490             {
18491                 i--;
18492                 len--;
18493             }
18494 
18495             // word too long?  restore i & len before splitting it
18496             if ((old_i-i+clen) >= width)
18497             {
18498                 i = old_i;
18499                 len = old_len;
18500             }
18501 
18502             // extra space?
18503             if (i && src[i-1] == ' ')
18504                 len--;
18505 
18506             if (src[i] != ' ' && src[i] != '\n')
18507             {
18508                 i--;
18509                 if (len)
18510                     len--;
18511             }
18512 
18513             // now append the newline and continuation sequence
18514             if (dest)
18515                 dest[len] = '\n';
18516             len++;
18517             if (dest)
18518                 strncpy(dest+len, cseq, cseq_len);
18519             len += cseq_len;
18520             line = cseq_len;
18521             clen = cseq_len;
18522             continue;
18523         }
18524 
18525         if (dest)
18526             dest[len] = src[i];
18527         len++;
18528         if (!ansi)
18529             line++;
18530         if (src[i] == '\n')
18531             line = 0;
18532         if (src[i] == 'm')
18533             ansi = 0;
18534     }
18535     if (dest && appData.debugMode)
18536     {
18537         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18538             count, width, line, len, *lp);
18539         show_bytes(debugFP, src, count);
18540         fprintf(debugFP, "\ndest: ");
18541         show_bytes(debugFP, dest, len);
18542         fprintf(debugFP, "\n");
18543     }
18544     *lp = dest ? line : old_line;
18545 
18546     return len;
18547 }
18548 
18549 // [HGM] vari: routines for shelving variations
18550 Boolean modeRestore = FALSE;
18551 
18552 void
18553 PushInner (int firstMove, int lastMove)
18554 {
18555 	int i, j, nrMoves = lastMove - firstMove;
18556 
18557 	// push current tail of game on stack
18558 	savedResult[storedGames] = gameInfo.result;
18559 	savedDetails[storedGames] = gameInfo.resultDetails;
18560 	gameInfo.resultDetails = NULL;
18561 	savedFirst[storedGames] = firstMove;
18562 	savedLast [storedGames] = lastMove;
18563 	savedFramePtr[storedGames] = framePtr;
18564 	framePtr -= nrMoves; // reserve space for the boards
18565 	for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18566 	    CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18567 	    for(j=0; j<MOVE_LEN; j++)
18568 		moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18569 	    for(j=0; j<2*MOVE_LEN; j++)
18570 		parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18571 	    timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18572 	    timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18573 	    pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18574 	    pvInfoList[firstMove+i-1].depth = 0;
18575 	    commentList[framePtr+i] = commentList[firstMove+i];
18576 	    commentList[firstMove+i] = NULL;
18577 	}
18578 
18579 	storedGames++;
18580 	forwardMostMove = firstMove; // truncate game so we can start variation
18581 }
18582 
18583 void
18584 PushTail (int firstMove, int lastMove)
18585 {
18586 	if(appData.icsActive) { // only in local mode
18587 		forwardMostMove = currentMove; // mimic old ICS behavior
18588 		return;
18589 	}
18590 	if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18591 
18592 	PushInner(firstMove, lastMove);
18593 	if(storedGames == 1) GreyRevert(FALSE);
18594 	if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18595 }
18596 
18597 void
18598 PopInner (Boolean annotate)
18599 {
18600 	int i, j, nrMoves;
18601 	char buf[8000], moveBuf[20];
18602 
18603 	ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18604 	storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18605 	nrMoves = savedLast[storedGames] - currentMove;
18606 	if(annotate) {
18607 		int cnt = 10;
18608 		if(!WhiteOnMove(currentMove))
18609 		  snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18610 		else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18611 		for(i=currentMove; i<forwardMostMove; i++) {
18612 			if(WhiteOnMove(i))
18613 			  snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18614 			else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18615 			strcat(buf, moveBuf);
18616 			if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18617 			if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18618 		}
18619 		strcat(buf, ")");
18620 	}
18621 	for(i=1; i<=nrMoves; i++) { // copy last variation back
18622 	    CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18623 	    for(j=0; j<MOVE_LEN; j++)
18624 		moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18625 	    for(j=0; j<2*MOVE_LEN; j++)
18626 		parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18627 	    timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18628 	    timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18629 	    pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18630 	    if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18631 	    commentList[currentMove+i] = commentList[framePtr+i];
18632 	    commentList[framePtr+i] = NULL;
18633 	}
18634 	if(annotate) AppendComment(currentMove+1, buf, FALSE);
18635 	framePtr = savedFramePtr[storedGames];
18636 	gameInfo.result = savedResult[storedGames];
18637 	if(gameInfo.resultDetails != NULL) {
18638 	    free(gameInfo.resultDetails);
18639       }
18640 	gameInfo.resultDetails = savedDetails[storedGames];
18641 	forwardMostMove = currentMove + nrMoves;
18642 }
18643 
18644 Boolean
18645 PopTail (Boolean annotate)
18646 {
18647 	if(appData.icsActive) return FALSE; // only in local mode
18648 	if(!storedGames) return FALSE; // sanity
18649 	CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18650 
18651 	PopInner(annotate);
18652 	if(currentMove < forwardMostMove) ForwardEvent(); else
18653 	HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18654 
18655 	if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18656 	return TRUE;
18657 }
18658 
18659 void
18660 CleanupTail ()
18661 {	// remove all shelved variations
18662 	int i;
18663 	for(i=0; i<storedGames; i++) {
18664 	    if(savedDetails[i])
18665 		free(savedDetails[i]);
18666 	    savedDetails[i] = NULL;
18667 	}
18668 	for(i=framePtr; i<MAX_MOVES; i++) {
18669 		if(commentList[i]) free(commentList[i]);
18670 		commentList[i] = NULL;
18671 	}
18672 	framePtr = MAX_MOVES-1;
18673 	storedGames = 0;
18674 }
18675 
18676 void
18677 LoadVariation (int index, char *text)
18678 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18679 	char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18680 	int level = 0, move;
18681 
18682 	if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18683 	// first find outermost bracketing variation
18684 	while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18685 	    if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18686 		if(*p == '{') wait = '}'; else
18687 		if(*p == '[') wait = ']'; else
18688 		if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18689 		if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18690 	    }
18691 	    if(*p == wait) wait = NULLCHAR; // closing ]} found
18692 	    p++;
18693 	}
18694 	if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18695 	if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18696 	end[1] = NULLCHAR; // clip off comment beyond variation
18697 	ToNrEvent(currentMove-1);
18698 	PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18699 	// kludge: use ParsePV() to append variation to game
18700 	move = currentMove;
18701 	ParsePV(start, TRUE, TRUE);
18702 	forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18703 	ClearPremoveHighlights();
18704 	CommentPopDown();
18705 	ToNrEvent(currentMove+1);
18706 }
18707 
18708 void
18709 LoadTheme ()
18710 {
18711     char *p, *q, buf[MSG_SIZ];
18712     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18713 	snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18714 	ParseArgsFromString(buf);
18715 	ActivateTheme(TRUE); // also redo colors
18716 	return;
18717     }
18718     p = nickName;
18719     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18720     {
18721 	int len;
18722 	q = appData.themeNames;
18723 	snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18724       if(appData.useBitmaps) {
18725 	snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18726 		appData.liteBackTextureFile, appData.darkBackTextureFile,
18727 		appData.liteBackTextureMode,
18728 		appData.darkBackTextureMode );
18729       } else {
18730 	snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18731 		Col2Text(2),   // lightSquareColor
18732 		Col2Text(3) ); // darkSquareColor
18733       }
18734       if(appData.useBorder) {
18735 	snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18736 		appData.border);
18737       } else {
18738 	snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18739       }
18740       if(appData.useFont) {
18741 	snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18742 		appData.renderPiecesWithFont,
18743 		appData.fontToPieceTable,
18744 		Col2Text(9),    // appData.fontBackColorWhite
18745 		Col2Text(10) ); // appData.fontForeColorBlack
18746       } else {
18747 	snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18748 		appData.pieceDirectory);
18749 	if(!appData.pieceDirectory[0])
18750 	  snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18751 		Col2Text(0),   // whitePieceColor
18752 		Col2Text(1) ); // blackPieceColor
18753       }
18754       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18755 		Col2Text(4),   // highlightSquareColor
18756 		Col2Text(5) ); // premoveHighlightColor
18757 	appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18758 	if(insert != q) insert[-1] = NULLCHAR;
18759 	snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18760 	if(q) 	free(q);
18761     }
18762     ActivateTheme(FALSE);
18763 }
18764